mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 04:01:02 +00:00
pallet-xcm: enhance reserve_transfer_assets to support remote reserves (#1672)
## Motivation `pallet-xcm` is the main user-facing interface for XCM functionality, including assets manipulation functions like `teleportAssets()` and `reserve_transfer_assets()` calls. While `teleportAsset()` works both ways, `reserve_transfer_assets()` works only for sending reserve-based assets to a remote destination and beneficiary when the reserve is the _local chain_. ## Solution This PR enhances `pallet_xcm::(limited_)reserve_withdraw_assets` to support transfers when reserves are other chains. This will allow complete, **bi-directional** reserve-based asset transfers user stories using `pallet-xcm`. Enables following scenarios: - transferring assets with local reserve (was previously supported iff asset used as fee also had local reserve - now it works in all cases), - transferring assets with reserve on destination, - transferring assets with reserve on remote/third-party chain (iff assets and fees have same remote reserve), - transferring assets with reserve different than the reserve of the asset to be used as fees - meaning can be used to transfer random asset with local/dest reserve while using DOT for fees on all involved chains, even if DOT local/dest reserve doesn't match asset reserve, - transferring assets with any type of local/dest reserve while using fees which can be teleported between involved chains. All of the above is done by pallet inner logic without the user having to specify which scenario/reserves/teleports/etc. The correct scenario and corresponding XCM programs are identified, and respectively, built automatically based on runtime configuration of trusted teleporters and trusted reserves. #### Current limitations: - while `fees` and "non-fee" `assets` CAN have different reserves (or fees CAN be teleported), the remaining "non-fee" `assets` CANNOT, among themselves, have different reserve locations (this is also implicitly enforced by `MAX_ASSETS_FOR_TRANSFER=2`, but this can be safely increased in the future). - `fees` and "non-fee" `assets` CANNOT have **different remote** reserves (this could also be supported in the future, but adds even more complexity while possibly not being worth it - we'll see what the future holds). Fixes https://github.com/paritytech/polkadot-sdk/issues/1584 Fixes https://github.com/paritytech/polkadot-sdk/issues/2055 --------- Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> Co-authored-by: Branislav Kontur <bkontur@gmail.com>
This commit is contained in:
@@ -16,15 +16,56 @@
|
||||
|
||||
use super::*;
|
||||
use bounded_collections::{ConstU32, WeakBoundedVec};
|
||||
use frame_benchmarking::{benchmarks, BenchmarkError, BenchmarkResult};
|
||||
use frame_support::weights::Weight;
|
||||
use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult};
|
||||
use frame_support::{traits::Currency, weights::Weight};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_std::prelude::*;
|
||||
use xcm::{latest::prelude::*, v2};
|
||||
|
||||
type RuntimeOrigin<T> = <T as frame_system::Config>::RuntimeOrigin;
|
||||
|
||||
// existential deposit multiplier
|
||||
const ED_MULTIPLIER: u32 = 100;
|
||||
|
||||
/// Pallet we're benchmarking here.
|
||||
pub struct Pallet<T: Config>(crate::Pallet<T>);
|
||||
|
||||
/// Trait that must be implemented by runtime to be able to benchmark pallet properly.
|
||||
pub trait Config: crate::Config {
|
||||
/// A `MultiLocation` that can be reached via `XcmRouter`. Used only in benchmarks.
|
||||
///
|
||||
/// If `None`, the benchmarks that depend on a reachable destination will be skipped.
|
||||
fn reachable_dest() -> Option<MultiLocation> {
|
||||
None
|
||||
}
|
||||
|
||||
/// A `(MultiAsset, MultiLocation)` pair representing asset and the destination it can be
|
||||
/// teleported to. Used only in benchmarks.
|
||||
///
|
||||
/// Implementation should also make sure `dest` is reachable/connected.
|
||||
///
|
||||
/// If `None`, the benchmarks that depend on this will be skipped.
|
||||
fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> {
|
||||
None
|
||||
}
|
||||
|
||||
/// A `(MultiAsset, MultiLocation)` pair representing asset and the destination it can be
|
||||
/// reserve-transferred to. Used only in benchmarks.
|
||||
///
|
||||
/// Implementation should also make sure `dest` is reachable/connected.
|
||||
///
|
||||
/// If `None`, the benchmarks that depend on this will be skipped.
|
||||
fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
where_clause {
|
||||
where
|
||||
T: pallet_balances::Config,
|
||||
<T as pallet_balances::Config>::Balance: From<u128> + Into<u128>,
|
||||
}
|
||||
send {
|
||||
let send_origin =
|
||||
T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
@@ -32,7 +73,7 @@ benchmarks! {
|
||||
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
|
||||
}
|
||||
let msg = Xcm(vec![ClearOrigin]);
|
||||
let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or(
|
||||
let versioned_dest: VersionedMultiLocation = T::reachable_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?
|
||||
.into();
|
||||
@@ -40,44 +81,82 @@ benchmarks! {
|
||||
}: _<RuntimeOrigin<T>>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg))
|
||||
|
||||
teleport_assets {
|
||||
let asset: MultiAsset = (Here, 10).into();
|
||||
let send_origin =
|
||||
T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone())
|
||||
let (asset, destination) = T::teleportable_asset_and_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?;
|
||||
|
||||
let transferred_amount = match &asset.fun {
|
||||
Fungible(amount) => *amount,
|
||||
_ => return Err(BenchmarkError::Stop("Benchmark asset not fungible")),
|
||||
}.into();
|
||||
let assets: MultiAssets = asset.into();
|
||||
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
// Give some multiple of the existential deposit
|
||||
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
assert!(balance >= transferred_amount);
|
||||
let _ = <pallet_balances::Pallet<T> as Currency<_>>::make_free_balance_be(&caller, balance);
|
||||
// verify initial balance
|
||||
assert_eq!(pallet_balances::Pallet::<T>::free_balance(&caller), balance);
|
||||
|
||||
let send_origin = RawOrigin::Signed(caller.clone());
|
||||
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
|
||||
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
|
||||
if !T::XcmTeleportFilter::contains(&(origin_location, vec![asset.clone()])) {
|
||||
if !T::XcmTeleportFilter::contains(&(origin_location, assets.clone().into_inner())) {
|
||||
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
|
||||
}
|
||||
|
||||
let recipient = [0u8; 32];
|
||||
let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?
|
||||
.into();
|
||||
let versioned_dest: VersionedMultiLocation = destination.into();
|
||||
let versioned_beneficiary: VersionedMultiLocation =
|
||||
AccountId32 { network: None, id: recipient.into() }.into();
|
||||
let versioned_assets: VersionedMultiAssets = asset.into();
|
||||
}: _<RuntimeOrigin<T>>(send_origin, Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0)
|
||||
let versioned_assets: VersionedMultiAssets = assets.into();
|
||||
}: _<RuntimeOrigin<T>>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0)
|
||||
verify {
|
||||
// verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees)
|
||||
assert!(pallet_balances::Pallet::<T>::free_balance(&caller) <= balance - transferred_amount);
|
||||
}
|
||||
|
||||
reserve_transfer_assets {
|
||||
let asset: MultiAsset = (Here, 10).into();
|
||||
let send_origin =
|
||||
T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone())
|
||||
let (asset, destination) = T::reserve_transferable_asset_and_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?;
|
||||
|
||||
let transferred_amount = match &asset.fun {
|
||||
Fungible(amount) => *amount,
|
||||
_ => return Err(BenchmarkError::Stop("Benchmark asset not fungible")),
|
||||
}.into();
|
||||
let assets: MultiAssets = asset.into();
|
||||
|
||||
let existential_deposit = T::ExistentialDeposit::get();
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
// Give some multiple of the existential deposit
|
||||
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
|
||||
assert!(balance >= transferred_amount);
|
||||
let _ = <pallet_balances::Pallet<T> as Currency<_>>::make_free_balance_be(&caller, balance);
|
||||
// verify initial balance
|
||||
assert_eq!(pallet_balances::Pallet::<T>::free_balance(&caller), balance);
|
||||
|
||||
let send_origin = RawOrigin::Signed(caller.clone());
|
||||
let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into())
|
||||
.map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
|
||||
if !T::XcmReserveTransferFilter::contains(&(origin_location, vec![asset.clone()])) {
|
||||
if !T::XcmReserveTransferFilter::contains(&(origin_location, assets.clone().into_inner())) {
|
||||
return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))
|
||||
}
|
||||
|
||||
let recipient = [0u8; 32];
|
||||
let versioned_dest: VersionedMultiLocation = T::ReachableDest::get().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?
|
||||
.into();
|
||||
let versioned_dest: VersionedMultiLocation = destination.into();
|
||||
let versioned_beneficiary: VersionedMultiLocation =
|
||||
AccountId32 { network: None, id: recipient.into() }.into();
|
||||
let versioned_assets: VersionedMultiAssets = asset.into();
|
||||
}: _<RuntimeOrigin<T>>(send_origin, Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0)
|
||||
let versioned_assets: VersionedMultiAssets = assets.into();
|
||||
}: _<RuntimeOrigin<T>>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0)
|
||||
verify {
|
||||
// verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees)
|
||||
assert!(pallet_balances::Pallet::<T>::free_balance(&caller) <= balance - transferred_amount);
|
||||
}
|
||||
|
||||
execute {
|
||||
let execute_origin =
|
||||
@@ -92,7 +171,7 @@ benchmarks! {
|
||||
}: _<RuntimeOrigin<T>>(execute_origin, Box::new(versioned_msg), Weight::zero())
|
||||
|
||||
force_xcm_version {
|
||||
let loc = T::ReachableDest::get().ok_or(
|
||||
let loc = T::reachable_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?;
|
||||
let xcm_version = 2;
|
||||
@@ -101,18 +180,18 @@ benchmarks! {
|
||||
force_default_xcm_version {}: _(RawOrigin::Root, Some(2))
|
||||
|
||||
force_subscribe_version_notify {
|
||||
let versioned_loc: VersionedMultiLocation = T::ReachableDest::get().ok_or(
|
||||
let versioned_loc: VersionedMultiLocation = T::reachable_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?
|
||||
.into();
|
||||
}: _(RawOrigin::Root, Box::new(versioned_loc))
|
||||
|
||||
force_unsubscribe_version_notify {
|
||||
let loc = T::ReachableDest::get().ok_or(
|
||||
let loc = T::reachable_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
|
||||
)?;
|
||||
let versioned_loc: VersionedMultiLocation = loc.into();
|
||||
let _ = Pallet::<T>::request_version_notify(loc);
|
||||
let _ = crate::Pallet::<T>::request_version_notify(loc);
|
||||
}: _(RawOrigin::Root, Box::new(versioned_loc))
|
||||
|
||||
force_suspension {}: _(RawOrigin::Root, true)
|
||||
@@ -122,7 +201,7 @@ benchmarks! {
|
||||
let loc = VersionedMultiLocation::from(MultiLocation::from(Parent));
|
||||
SupportedVersion::<T>::insert(old_version, loc, old_version);
|
||||
}: {
|
||||
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero());
|
||||
crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero());
|
||||
}
|
||||
|
||||
migrate_version_notifiers {
|
||||
@@ -130,22 +209,22 @@ benchmarks! {
|
||||
let loc = VersionedMultiLocation::from(MultiLocation::from(Parent));
|
||||
VersionNotifiers::<T>::insert(old_version, loc, 0);
|
||||
}: {
|
||||
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero());
|
||||
crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero());
|
||||
}
|
||||
|
||||
already_notified_target {
|
||||
let loc = T::ReachableDest::get().ok_or(
|
||||
let loc = T::reachable_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads(1))),
|
||||
)?;
|
||||
let loc = VersionedMultiLocation::from(loc);
|
||||
let current_version = T::AdvertisedXcmVersion::get();
|
||||
VersionNotifyTargets::<T>::insert(current_version, loc, (0, Weight::zero(), current_version));
|
||||
}: {
|
||||
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero());
|
||||
crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero());
|
||||
}
|
||||
|
||||
notify_current_targets {
|
||||
let loc = T::ReachableDest::get().ok_or(
|
||||
let loc = T::reachable_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))),
|
||||
)?;
|
||||
let loc = VersionedMultiLocation::from(loc);
|
||||
@@ -153,7 +232,7 @@ benchmarks! {
|
||||
let old_version = current_version - 1;
|
||||
VersionNotifyTargets::<T>::insert(current_version, loc, (0, Weight::zero(), old_version));
|
||||
}: {
|
||||
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero());
|
||||
crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero());
|
||||
}
|
||||
|
||||
notify_target_migration_fail {
|
||||
@@ -167,7 +246,7 @@ benchmarks! {
|
||||
let current_version = T::AdvertisedXcmVersion::get();
|
||||
VersionNotifyTargets::<T>::insert(current_version, bad_loc, (0, Weight::zero(), current_version));
|
||||
}: {
|
||||
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
|
||||
crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
|
||||
}
|
||||
|
||||
migrate_version_notify_targets {
|
||||
@@ -176,18 +255,18 @@ benchmarks! {
|
||||
let loc = VersionedMultiLocation::from(MultiLocation::from(Parent));
|
||||
VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), current_version));
|
||||
}: {
|
||||
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
|
||||
crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
|
||||
}
|
||||
|
||||
migrate_and_notify_old_targets {
|
||||
let loc = T::ReachableDest::get().ok_or(
|
||||
let loc = T::reachable_dest().ok_or(
|
||||
BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))),
|
||||
)?;
|
||||
let loc = VersionedMultiLocation::from(loc);
|
||||
let old_version = T::AdvertisedXcmVersion::get() - 1;
|
||||
VersionNotifyTargets::<T>::insert(old_version, loc, (0, Weight::zero(), old_version));
|
||||
}: {
|
||||
Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
|
||||
crate::Pallet::<T>::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero());
|
||||
}
|
||||
|
||||
new_query {
|
||||
@@ -195,14 +274,14 @@ benchmarks! {
|
||||
let timeout = 1u32.into();
|
||||
let match_querier = MultiLocation::from(Here);
|
||||
}: {
|
||||
Pallet::<T>::new_query(responder, timeout, match_querier);
|
||||
crate::Pallet::<T>::new_query(responder, timeout, match_querier);
|
||||
}
|
||||
|
||||
take_response {
|
||||
let responder = MultiLocation::from(Parent);
|
||||
let timeout = 1u32.into();
|
||||
let match_querier = MultiLocation::from(Here);
|
||||
let query_id = Pallet::<T>::new_query(responder, timeout, match_querier);
|
||||
let query_id = crate::Pallet::<T>::new_query(responder, timeout, match_querier);
|
||||
let infos = (0 .. xcm::v3::MaxPalletsInfo::get()).map(|_| PalletInfo::new(
|
||||
u32::MAX,
|
||||
(0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::<Vec<_>>().try_into().unwrap(),
|
||||
@@ -211,10 +290,10 @@ benchmarks! {
|
||||
u32::MAX,
|
||||
u32::MAX,
|
||||
).unwrap()).collect::<Vec<_>>();
|
||||
Pallet::<T>::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap()));
|
||||
crate::Pallet::<T>::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap()));
|
||||
|
||||
}: {
|
||||
<Pallet::<T> as QueryHandler>::take_response(query_id);
|
||||
<crate::Pallet::<T> as QueryHandler>::take_response(query_id);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
pub mod benchmarking;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
@@ -55,9 +55,9 @@ use xcm_builder::{
|
||||
};
|
||||
use xcm_executor::{
|
||||
traits::{
|
||||
CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin, DropAssets, MatchesFungible,
|
||||
OnResponse, Properties, QueryHandler, QueryResponseStatus, VersionChangeNotifier,
|
||||
WeightBounds,
|
||||
AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin,
|
||||
DropAssets, MatchesFungible, OnResponse, Properties, QueryHandler, QueryResponseStatus,
|
||||
TransactAsset, TransferType, VersionChangeNotifier, WeightBounds, XcmAssetTransfers,
|
||||
},
|
||||
Assets,
|
||||
};
|
||||
@@ -222,7 +222,7 @@ pub mod pallet {
|
||||
type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as Config>::RuntimeCall>)>;
|
||||
|
||||
/// Something to execute an XCM message.
|
||||
type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall>;
|
||||
type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall> + XcmAssetTransfers;
|
||||
|
||||
/// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass.
|
||||
type XcmTeleportFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;
|
||||
@@ -275,12 +275,6 @@ pub mod pallet {
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// A `MultiLocation` that can be reached via `XcmRouter`. Used only in benchmarks.
|
||||
///
|
||||
/// If `None`, the benchmarks that depend on a reachable destination will be skipped.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type ReachableDest: Get<Option<MultiLocation>>;
|
||||
}
|
||||
|
||||
impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
|
||||
@@ -531,8 +525,8 @@ pub mod pallet {
|
||||
NoSubscription,
|
||||
/// The location is invalid since it already has a subscription from us.
|
||||
AlreadySubscribed,
|
||||
/// Invalid asset for the operation.
|
||||
InvalidAsset,
|
||||
/// Could not check-out the assets for teleportation to the destination chain.
|
||||
CannotCheckOutTeleport,
|
||||
/// The owner does not own (all) of the asset that they wish to do the operation on.
|
||||
LowBalance,
|
||||
/// The asset owner has too many locks on the asset.
|
||||
@@ -545,6 +539,16 @@ pub mod pallet {
|
||||
LockNotFound,
|
||||
/// The unlock operation cannot succeed because there are still consumers of the lock.
|
||||
InUse,
|
||||
/// Invalid non-concrete asset.
|
||||
InvalidAssetNotConcrete,
|
||||
/// Invalid asset, reserve chain could not be determined for it.
|
||||
InvalidAssetUnknownReserve,
|
||||
/// Invalid asset, do not support remote asset reserves with different fees reserves.
|
||||
InvalidAssetUnsupportedReserve,
|
||||
/// Too many assets with different reserve locations have been attempted for transfer.
|
||||
TooManyReserves,
|
||||
/// Local XCM execution of asset transfer incomplete.
|
||||
LocalExecutionIncomplete,
|
||||
}
|
||||
|
||||
impl<T: Config> From<SendError> for Error<T> {
|
||||
@@ -557,6 +561,15 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> From<AssetTransferError> for Error<T> {
|
||||
fn from(e: AssetTransferError) -> Self {
|
||||
match e {
|
||||
AssetTransferError::NotConcrete => Error::<T>::InvalidAssetNotConcrete,
|
||||
AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The status of a query.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub enum QueryStatus<BlockNumber> {
|
||||
@@ -907,11 +920,7 @@ pub mod pallet {
|
||||
let mut message = Xcm(vec![
|
||||
WithdrawAsset(assets),
|
||||
SetFeesMode { jit_withdraw: true },
|
||||
InitiateTeleport {
|
||||
assets: Wild(AllCounted(count)),
|
||||
dest,
|
||||
xcm: Xcm(vec![]),
|
||||
},
|
||||
InitiateTeleport { assets: Wild(AllCounted(count)), dest, xcm: Xcm(vec![]) },
|
||||
]);
|
||||
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w))
|
||||
}
|
||||
@@ -954,6 +963,8 @@ pub mod pallet {
|
||||
match (maybe_assets, maybe_dest) {
|
||||
(Ok(assets), Ok(dest)) => {
|
||||
use sp_std::vec;
|
||||
// heaviest version of locally executed XCM program: equivalent in weight to
|
||||
// transfer assets to SA, reanchor them, extend XCM program, and send onward XCM
|
||||
let mut message = Xcm(vec![
|
||||
SetFeesMode { jit_withdraw: true },
|
||||
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
|
||||
@@ -1114,6 +1125,8 @@ pub mod pallet {
|
||||
match (maybe_assets, maybe_dest) {
|
||||
(Ok(assets), Ok(dest)) => {
|
||||
use sp_std::vec;
|
||||
// heaviest version of locally executed XCM program: equivalent in weight to
|
||||
// transfer assets to SA, reanchor them, extend XCM program, and send onward XCM
|
||||
let mut message = Xcm(vec![
|
||||
SetFeesMode { jit_withdraw: true },
|
||||
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
|
||||
@@ -1273,6 +1286,33 @@ impl<T: Config> QueryHandler for Pallet<T> {
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Validate `assets` to be reserve-transferred and return their reserve location.
|
||||
fn validate_assets_and_find_reserve(
|
||||
assets: &[MultiAsset],
|
||||
dest: &MultiLocation,
|
||||
) -> Result<TransferType, Error<T>> {
|
||||
let mut reserve = None;
|
||||
for asset in assets.iter() {
|
||||
if let Fungible(x) = asset.fun {
|
||||
// If fungible asset, ensure non-zero amount.
|
||||
ensure!(!x.is_zero(), Error::<T>::Empty);
|
||||
}
|
||||
let transfer_type =
|
||||
T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
|
||||
// Ensure asset is not teleportable to `dest`.
|
||||
ensure!(transfer_type != TransferType::Teleport, Error::<T>::Filtered);
|
||||
if let Some(reserve) = reserve.as_ref() {
|
||||
// Ensure transfer for multiple assets uses same reserve location (only fee may have
|
||||
// different reserve location)
|
||||
ensure!(reserve == &transfer_type, Error::<T>::TooManyReserves);
|
||||
} else {
|
||||
// asset reserve identified
|
||||
reserve = Some(transfer_type);
|
||||
}
|
||||
}
|
||||
reserve.ok_or(Error::<T>::Empty)
|
||||
}
|
||||
|
||||
fn do_reserve_transfer_assets(
|
||||
origin: OriginFor<T>,
|
||||
dest: Box<VersionedMultiLocation>,
|
||||
@@ -1286,35 +1326,75 @@ impl<T: Config> Pallet<T> {
|
||||
let beneficiary: MultiLocation =
|
||||
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
|
||||
log::trace!(
|
||||
target: "xcm::pallet_xcm::do_reserve_transfer_assets",
|
||||
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}",
|
||||
origin_location, dest, beneficiary, assets, fee_asset_item,
|
||||
);
|
||||
|
||||
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
|
||||
let value = (origin_location, assets.into_inner());
|
||||
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
|
||||
let (origin_location, assets) = value;
|
||||
let context = T::UniversalLocation::get();
|
||||
let fees = assets
|
||||
.get(fee_asset_item as usize)
|
||||
.ok_or(Error::<T>::Empty)?
|
||||
.clone()
|
||||
.reanchored(&dest, context)
|
||||
.map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
let max_assets = assets.len() as u32;
|
||||
let assets: MultiAssets = assets.into();
|
||||
let xcm = Xcm(vec![
|
||||
BuyExecution { fees, weight_limit },
|
||||
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
|
||||
]);
|
||||
let mut message = Xcm(vec![
|
||||
SetFeesMode { jit_withdraw: true },
|
||||
TransferReserveAsset { assets, dest, xcm },
|
||||
]);
|
||||
let weight =
|
||||
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
|
||||
let hash = message.using_encoded(sp_io::hashing::blake2_256);
|
||||
let outcome =
|
||||
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight);
|
||||
Self::deposit_event(Event::Attempted { outcome });
|
||||
Ok(())
|
||||
let (origin_location, mut assets) = value;
|
||||
|
||||
if fee_asset_item as usize >= assets.len() {
|
||||
return Err(Error::<T>::Empty.into())
|
||||
}
|
||||
let fees = assets.swap_remove(fee_asset_item as usize);
|
||||
let fees_transfer_type =
|
||||
T::XcmExecutor::determine_for(&fees, &dest).map_err(Error::<T>::from)?;
|
||||
let assets_transfer_type = if assets.is_empty() {
|
||||
// Single asset to transfer (one used for fees where transfer type is determined above).
|
||||
ensure!(fees_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
|
||||
fees_transfer_type
|
||||
} else {
|
||||
// Find reserve for non-fee assets.
|
||||
Self::validate_assets_and_find_reserve(&assets, &dest)?
|
||||
};
|
||||
|
||||
// local and remote XCM programs to potentially handle fees separately
|
||||
let separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>;
|
||||
if fees_transfer_type == assets_transfer_type {
|
||||
// Same reserve location (fees not teleportable), we can batch together fees and assets
|
||||
// in same reserve-based-transfer.
|
||||
assets.push(fees.clone());
|
||||
// no need for custom fees instructions, fees are batched with assets
|
||||
separate_fees_instructions = None;
|
||||
} else {
|
||||
// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered by
|
||||
// branch above). The reason for this is that we'd need to send XCMs to separate chains
|
||||
// with no guarantee of delivery order on final destination; therefore we cannot
|
||||
// guarantee to have fees in place on final destination chain to pay for assets
|
||||
// transfer.
|
||||
ensure!(
|
||||
!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
|
||||
Error::<T>::InvalidAssetUnsupportedReserve
|
||||
);
|
||||
let fees = fees.clone();
|
||||
let weight_limit = weight_limit.clone();
|
||||
// build fees transfer instructions to be added to assets transfers XCM programs
|
||||
separate_fees_instructions = Some(match fees_transfer_type {
|
||||
TransferType::LocalReserve =>
|
||||
Self::local_reserve_fees_instructions(dest, fees, weight_limit)?,
|
||||
TransferType::DestinationReserve =>
|
||||
Self::destination_reserve_fees_instructions(dest, fees, weight_limit)?,
|
||||
TransferType::Teleport =>
|
||||
Self::teleport_fees_instructions(dest, fees, weight_limit)?,
|
||||
TransferType::RemoteReserve(_) =>
|
||||
return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
|
||||
});
|
||||
};
|
||||
|
||||
Self::build_and_execute_xcm_transfer_type(
|
||||
origin_location,
|
||||
dest,
|
||||
beneficiary,
|
||||
assets,
|
||||
assets_transfer_type,
|
||||
fees,
|
||||
separate_fees_instructions,
|
||||
weight_limit,
|
||||
)
|
||||
}
|
||||
|
||||
fn do_teleport_assets(
|
||||
@@ -1335,31 +1415,384 @@ impl<T: Config> Pallet<T> {
|
||||
let value = (origin_location, assets.into_inner());
|
||||
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
|
||||
let (origin_location, assets) = value;
|
||||
for asset in assets.iter() {
|
||||
let transfer_type =
|
||||
T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
|
||||
ensure!(matches!(transfer_type, TransferType::Teleport), Error::<T>::Filtered);
|
||||
}
|
||||
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
|
||||
|
||||
Self::build_and_execute_xcm_transfer_type(
|
||||
origin_location,
|
||||
dest,
|
||||
beneficiary,
|
||||
assets,
|
||||
TransferType::Teleport,
|
||||
fees,
|
||||
None,
|
||||
weight_limit,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_and_execute_xcm_transfer_type(
|
||||
origin: MultiLocation,
|
||||
dest: MultiLocation,
|
||||
beneficiary: MultiLocation,
|
||||
assets: Vec<MultiAsset>,
|
||||
transfer_type: TransferType,
|
||||
fees: MultiAsset,
|
||||
separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
|
||||
weight_limit: WeightLimit,
|
||||
) -> DispatchResult {
|
||||
log::trace!(
|
||||
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
|
||||
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
|
||||
fees {:?}, fees_xcm: {:?}, weight_limit: {:?}",
|
||||
origin, dest, beneficiary, assets, transfer_type, fees, separate_fees_instructions, weight_limit,
|
||||
);
|
||||
let (mut local_xcm, remote_xcm) = match transfer_type {
|
||||
TransferType::LocalReserve => {
|
||||
let (local, remote) = Self::local_reserve_transfer_programs(
|
||||
dest,
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
separate_fees_instructions,
|
||||
weight_limit,
|
||||
)?;
|
||||
(local, Some(remote))
|
||||
},
|
||||
TransferType::DestinationReserve => {
|
||||
let (local, remote) = Self::destination_reserve_transfer_programs(
|
||||
dest,
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
separate_fees_instructions,
|
||||
weight_limit,
|
||||
)?;
|
||||
(local, Some(remote))
|
||||
},
|
||||
TransferType::RemoteReserve(reserve) => (
|
||||
Self::remote_reserve_transfer_program(
|
||||
reserve,
|
||||
dest,
|
||||
beneficiary,
|
||||
assets,
|
||||
fees,
|
||||
weight_limit,
|
||||
)?,
|
||||
None,
|
||||
),
|
||||
TransferType::Teleport => (
|
||||
Self::teleport_assets_program(dest, beneficiary, assets, fees, weight_limit)?,
|
||||
None,
|
||||
),
|
||||
};
|
||||
let weight =
|
||||
T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
|
||||
let hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
|
||||
let outcome =
|
||||
T::XcmExecutor::execute_xcm_in_credit(origin, local_xcm, hash, weight, weight);
|
||||
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
|
||||
if let Some(remote_xcm) = remote_xcm {
|
||||
outcome.ensure_complete().map_err(|_| Error::<T>::LocalExecutionIncomplete)?;
|
||||
|
||||
let (ticket, price) = validate_send::<T::XcmRouter>(dest, remote_xcm.clone())
|
||||
.map_err(Error::<T>::from)?;
|
||||
if origin != Here.into_location() {
|
||||
Self::charge_fees(origin, price).map_err(|_| Error::<T>::FeesNotMet)?;
|
||||
}
|
||||
let message_id = T::XcmRouter::deliver(ticket).map_err(Error::<T>::from)?;
|
||||
|
||||
let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
|
||||
Self::deposit_event(e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn local_reserve_fees_instructions(
|
||||
dest: MultiLocation,
|
||||
fees: MultiAsset,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
|
||||
let context = T::UniversalLocation::get();
|
||||
let fees = assets
|
||||
.get(fee_asset_item as usize)
|
||||
.ok_or(Error::<T>::Empty)?
|
||||
let reanchored_fees = fees
|
||||
.clone()
|
||||
.reanchored(&dest, context)
|
||||
.map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
let max_assets = assets.len() as u32;
|
||||
|
||||
let local_execute_xcm = Xcm(vec![
|
||||
// move `fees` to `dest`s local sovereign account
|
||||
TransferAsset { assets: fees.into(), beneficiary: dest },
|
||||
]);
|
||||
let xcm_on_dest = Xcm(vec![
|
||||
// let (dest) chain know `fees` are in its SA on reserve
|
||||
ReserveAssetDeposited(reanchored_fees.clone().into()),
|
||||
// buy exec using `fees` in holding deposited in above instruction
|
||||
BuyExecution { fees: reanchored_fees, weight_limit },
|
||||
]);
|
||||
Ok((local_execute_xcm, xcm_on_dest))
|
||||
}
|
||||
|
||||
fn local_reserve_transfer_programs(
|
||||
dest: MultiLocation,
|
||||
beneficiary: MultiLocation,
|
||||
assets: Vec<MultiAsset>,
|
||||
fees: MultiAsset,
|
||||
separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
|
||||
// max assets is `assets` (+ potentially separately handled fee)
|
||||
let max_assets =
|
||||
assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0);
|
||||
let assets: MultiAssets = assets.into();
|
||||
let xcm = Xcm(vec![
|
||||
let context = T::UniversalLocation::get();
|
||||
let mut reanchored_assets = assets.clone();
|
||||
reanchored_assets
|
||||
.reanchor(&dest, context)
|
||||
.map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
|
||||
// fees are either handled through dedicated instructions, or batched together with assets
|
||||
let fees_already_handled = separate_fees_instructions.is_some();
|
||||
let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions
|
||||
.map(|(local, remote)| (local.into_inner(), remote.into_inner()))
|
||||
.unwrap_or_default();
|
||||
|
||||
// start off with any necessary local fees specific instructions
|
||||
let mut local_execute_xcm = fees_local_xcm;
|
||||
// move `assets` to `dest`s local sovereign account
|
||||
local_execute_xcm.push(TransferAsset { assets, beneficiary: dest });
|
||||
|
||||
// on destination chain, start off with custom fee instructions
|
||||
let mut xcm_on_dest = fees_remote_xcm;
|
||||
// continue with rest of assets
|
||||
xcm_on_dest.extend_from_slice(&[
|
||||
// let (dest) chain know assets are in its SA on reserve
|
||||
ReserveAssetDeposited(reanchored_assets),
|
||||
// following instructions are not exec'ed on behalf of origin chain anymore
|
||||
ClearOrigin,
|
||||
]);
|
||||
if !fees_already_handled {
|
||||
// no custom fees instructions, they are batched together with `assets` transfer;
|
||||
// BuyExecution happens after receiving all `assets`
|
||||
let reanchored_fees =
|
||||
fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
// buy execution using `fees` batched together with above `reanchored_assets`
|
||||
xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit });
|
||||
}
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
|
||||
|
||||
Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest)))
|
||||
}
|
||||
|
||||
fn destination_reserve_fees_instructions(
|
||||
dest: MultiLocation,
|
||||
fees: MultiAsset,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
|
||||
let context = T::UniversalLocation::get();
|
||||
let reanchored_fees = fees
|
||||
.clone()
|
||||
.reanchored(&dest, context)
|
||||
.map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
let fees: MultiAssets = fees.into();
|
||||
|
||||
let local_execute_xcm = Xcm(vec![
|
||||
// withdraw reserve-based fees (derivatives)
|
||||
WithdrawAsset(fees.clone()),
|
||||
// burn derivatives
|
||||
BurnAsset(fees),
|
||||
]);
|
||||
let xcm_on_dest = Xcm(vec![
|
||||
// withdraw `fees` from origin chain's sovereign account
|
||||
WithdrawAsset(reanchored_fees.clone().into()),
|
||||
// buy exec using `fees` in holding withdrawn in above instruction
|
||||
BuyExecution { fees: reanchored_fees, weight_limit },
|
||||
]);
|
||||
Ok((local_execute_xcm, xcm_on_dest))
|
||||
}
|
||||
|
||||
fn destination_reserve_transfer_programs(
|
||||
dest: MultiLocation,
|
||||
beneficiary: MultiLocation,
|
||||
assets: Vec<MultiAsset>,
|
||||
fees: MultiAsset,
|
||||
separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
|
||||
// max assets is `assets` (+ potentially separately handled fee)
|
||||
let max_assets =
|
||||
assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0);
|
||||
let assets: MultiAssets = assets.into();
|
||||
let context = T::UniversalLocation::get();
|
||||
let mut reanchored_assets = assets.clone();
|
||||
reanchored_assets
|
||||
.reanchor(&dest, context)
|
||||
.map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
|
||||
// fees are either handled through dedicated instructions, or batched together with assets
|
||||
let fees_already_handled = separate_fees_instructions.is_some();
|
||||
let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions
|
||||
.map(|(local, remote)| (local.into_inner(), remote.into_inner()))
|
||||
.unwrap_or_default();
|
||||
|
||||
// start off with any necessary local fees specific instructions
|
||||
let mut local_execute_xcm = fees_local_xcm;
|
||||
// continue with rest of assets
|
||||
local_execute_xcm.extend_from_slice(&[
|
||||
// withdraw reserve-based assets
|
||||
WithdrawAsset(assets.clone()),
|
||||
// burn reserve-based assets
|
||||
BurnAsset(assets),
|
||||
]);
|
||||
|
||||
// on destination chain, start off with custom fee instructions
|
||||
let mut xcm_on_dest = fees_remote_xcm;
|
||||
// continue with rest of assets
|
||||
xcm_on_dest.extend_from_slice(&[
|
||||
// withdraw `assets` from origin chain's sovereign account
|
||||
WithdrawAsset(reanchored_assets),
|
||||
// following instructions are not exec'ed on behalf of origin chain anymore
|
||||
ClearOrigin,
|
||||
]);
|
||||
if !fees_already_handled {
|
||||
// no custom fees instructions, they are batched together with `assets` transfer;
|
||||
// BuyExecution happens after receiving all `assets`
|
||||
let reanchored_fees =
|
||||
fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
// buy execution using `fees` batched together with above `reanchored_assets`
|
||||
xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit });
|
||||
}
|
||||
// deposit all remaining assets in holding to `beneficiary` location
|
||||
xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });
|
||||
|
||||
Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest)))
|
||||
}
|
||||
|
||||
// function assumes fees and assets have the same remote reserve
|
||||
fn remote_reserve_transfer_program(
|
||||
reserve: MultiLocation,
|
||||
dest: MultiLocation,
|
||||
beneficiary: MultiLocation,
|
||||
assets: Vec<MultiAsset>,
|
||||
fees: MultiAsset,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
|
||||
let max_assets = assets.len() as u32;
|
||||
let context = T::UniversalLocation::get();
|
||||
// we spend up to half of fees for execution on reserve and other half for execution on
|
||||
// destination
|
||||
let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
|
||||
// identifies fee item as seen by `reserve` - to be used at reserve chain
|
||||
let reserve_fees = fees_half_1
|
||||
.reanchored(&reserve, context)
|
||||
.map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
// identifies fee item as seen by `dest` - to be used at destination chain
|
||||
let dest_fees =
|
||||
fees_half_2.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
// identifies `dest` as seen by `reserve`
|
||||
let dest = dest.reanchored(&reserve, context).map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
// xcm to be executed at dest
|
||||
let xcm_on_dest = Xcm(vec![
|
||||
BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() },
|
||||
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
|
||||
]);
|
||||
// xcm to be executed on reserve
|
||||
let xcm_on_reserve = Xcm(vec![
|
||||
BuyExecution { fees: reserve_fees, weight_limit },
|
||||
DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
|
||||
]);
|
||||
Ok(Xcm(vec![
|
||||
WithdrawAsset(assets.into()),
|
||||
InitiateReserveWithdraw {
|
||||
assets: Wild(AllCounted(max_assets)),
|
||||
reserve,
|
||||
xcm: xcm_on_reserve,
|
||||
},
|
||||
]))
|
||||
}
|
||||
|
||||
fn teleport_fees_instructions(
|
||||
dest: MultiLocation,
|
||||
fees: MultiAsset,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
|
||||
let context = T::UniversalLocation::get();
|
||||
let reanchored_fees = fees
|
||||
.clone()
|
||||
.reanchored(&dest, context)
|
||||
.map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
|
||||
// XcmContext irrelevant in teleports checks
|
||||
let dummy_context =
|
||||
XcmContext { origin: None, message_id: Default::default(), topic: None };
|
||||
// We should check that the asset can actually be teleported out (for this to
|
||||
// be in error, there would need to be an accounting violation by ourselves,
|
||||
// so it's unlikely, but we don't want to allow that kind of bug to leak into
|
||||
// a trusted chain.
|
||||
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
|
||||
&dest,
|
||||
&fees,
|
||||
&dummy_context,
|
||||
)
|
||||
.map_err(|_| Error::<T>::CannotCheckOutTeleport)?;
|
||||
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
|
||||
&dest,
|
||||
&fees,
|
||||
&dummy_context,
|
||||
);
|
||||
|
||||
let fees: MultiAssets = fees.into();
|
||||
let local_execute_xcm = Xcm(vec![
|
||||
// withdraw fees
|
||||
WithdrawAsset(fees.clone()),
|
||||
// burn fees
|
||||
BurnAsset(fees),
|
||||
]);
|
||||
let xcm_on_dest = Xcm(vec![
|
||||
// (dest) chain receive teleported assets burned on origin chain
|
||||
ReceiveTeleportedAsset(reanchored_fees.clone().into()),
|
||||
// buy exec using `fees` in holding received in above instruction
|
||||
BuyExecution { fees: reanchored_fees, weight_limit },
|
||||
]);
|
||||
Ok((local_execute_xcm, xcm_on_dest))
|
||||
}
|
||||
|
||||
fn teleport_assets_program(
|
||||
dest: MultiLocation,
|
||||
beneficiary: MultiLocation,
|
||||
assets: Vec<MultiAsset>,
|
||||
mut fees: MultiAsset,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
|
||||
let context = T::UniversalLocation::get();
|
||||
fees.reanchor(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
|
||||
let max_assets = assets.len() as u32;
|
||||
let xcm_on_dest = Xcm(vec![
|
||||
BuyExecution { fees, weight_limit },
|
||||
DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
|
||||
]);
|
||||
let mut message = Xcm(vec![
|
||||
WithdrawAsset(assets),
|
||||
Ok(Xcm(vec![
|
||||
WithdrawAsset(assets.into()),
|
||||
SetFeesMode { jit_withdraw: true },
|
||||
InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm },
|
||||
]);
|
||||
let weight =
|
||||
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
|
||||
let hash = message.using_encoded(sp_io::hashing::blake2_256);
|
||||
let outcome =
|
||||
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight);
|
||||
Self::deposit_event(Event::Attempted { outcome });
|
||||
Ok(())
|
||||
InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
|
||||
]))
|
||||
}
|
||||
|
||||
/// Halve `fees` fungible amount.
|
||||
pub(crate) fn halve_fees(fees: MultiAsset) -> Result<(MultiAsset, MultiAsset), Error<T>> {
|
||||
match fees.fun {
|
||||
Fungible(amount) => {
|
||||
let fee1 = amount.saturating_div(2);
|
||||
let fee2 = amount.saturating_sub(fee1);
|
||||
ensure!(fee1 > 0, Error::<T>::FeesNotMet);
|
||||
ensure!(fee2 > 0, Error::<T>::FeesNotMet);
|
||||
Ok((MultiAsset::from((fees.id, fee1)), MultiAsset::from((fees.id, fee2))))
|
||||
},
|
||||
NonFungible(_) => Err(Error::<T>::FeesNotMet),
|
||||
}
|
||||
}
|
||||
|
||||
/// Will always make progress, and will do its best not to use much more than `weight_cutoff`
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
construct_runtime, match_types, parameter_types,
|
||||
traits::{ConstU32, Everything, EverythingBut, Nothing},
|
||||
traits::{
|
||||
AsEnsureOriginWithArg, ConstU128, ConstU32, Equals, Everything, EverythingBut, Nothing,
|
||||
},
|
||||
weights::Weight,
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
@@ -32,11 +34,15 @@ use xcm::prelude::*;
|
||||
use xcm_builder::{
|
||||
AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
|
||||
AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia,
|
||||
ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible,
|
||||
FixedWeightBounds, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32,
|
||||
ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, DescribeAllTerminal,
|
||||
FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, HashedDescription, IsConcrete,
|
||||
MatchedConvertedConcreteId, NoChecking, SignedAccountId32AsNative, SignedToAccountId32,
|
||||
SovereignSignedViaLocation, TakeWeightCredit, XcmFeeManagerFromComponents, XcmFeeToAccount,
|
||||
};
|
||||
use xcm_executor::XcmExecutor;
|
||||
use xcm_executor::{
|
||||
traits::{Identity, JustTry},
|
||||
XcmExecutor,
|
||||
};
|
||||
|
||||
use crate::{self as pallet_xcm, TestWeightInfo};
|
||||
|
||||
@@ -137,6 +143,7 @@ construct_runtime!(
|
||||
{
|
||||
System: frame_system::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
Assets: pallet_assets::{Pallet, Call, Storage, Config<T>, Event<T>},
|
||||
ParasOrigin: origin::{Pallet, Origin},
|
||||
XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event<T>, Origin, Config<T>},
|
||||
TestNotifier: pallet_test_notifier::{Pallet, Call, Event<T>},
|
||||
@@ -179,13 +186,13 @@ impl SendXcm for TestSendXcmErrX8 {
|
||||
type Ticket = (MultiLocation, Xcm<()>);
|
||||
fn validate(
|
||||
dest: &mut Option<MultiLocation>,
|
||||
msg: &mut Option<Xcm<()>>,
|
||||
_: &mut Option<Xcm<()>>,
|
||||
) -> SendResult<(MultiLocation, Xcm<()>)> {
|
||||
let (dest, msg) = (dest.take().unwrap(), msg.take().unwrap());
|
||||
if dest.len() == 8 {
|
||||
if dest.as_ref().unwrap().len() == 8 {
|
||||
dest.take();
|
||||
Err(SendError::Transport("Destination location full"))
|
||||
} else {
|
||||
Ok(((dest, msg), MultiAssets::new()))
|
||||
Err(SendError::NotApplicable)
|
||||
}
|
||||
}
|
||||
fn deliver(pair: (MultiLocation, Xcm<()>)) -> Result<XcmHash, SendError> {
|
||||
@@ -280,18 +287,135 @@ impl pallet_balances::Config for Test {
|
||||
type MaxFreezes = ConstU32<0>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
/// Simple conversion of `u32` into an `AssetId` for use in benchmarking.
|
||||
pub struct XcmBenchmarkHelper;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl pallet_assets::BenchmarkHelper<MultiLocation> for XcmBenchmarkHelper {
|
||||
fn create_asset_id_parameter(id: u32) -> MultiLocation {
|
||||
MultiLocation { parents: 1, interior: X1(Parachain(id)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_assets::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = Balance;
|
||||
type AssetId = MultiLocation;
|
||||
type AssetIdParameter = MultiLocation;
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>;
|
||||
type ForceOrigin = EnsureRoot<AccountId>;
|
||||
type AssetDeposit = ConstU128<1>;
|
||||
type AssetAccountDeposit = ConstU128<10>;
|
||||
type MetadataDepositBase = ConstU128<1>;
|
||||
type MetadataDepositPerByte = ConstU128<1>;
|
||||
type ApprovalDeposit = ConstU128<1>;
|
||||
type StringLimit = ConstU32<50>;
|
||||
type Freezer = ();
|
||||
type WeightInfo = ();
|
||||
type CallbackHandle = ();
|
||||
type Extra = ();
|
||||
type RemoveItemsLimit = ConstU32<5>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = XcmBenchmarkHelper;
|
||||
}
|
||||
|
||||
// This child parachain is a system parachain trusted to teleport native token.
|
||||
pub const SOME_SYSTEM_PARA: u32 = 1001;
|
||||
|
||||
// This child parachain acts as trusted reserve for its assets in tests.
|
||||
// USDT allowed to teleport to/from here.
|
||||
pub const FOREIGN_ASSET_RESERVE_PARA_ID: u32 = 2001;
|
||||
// Inner junction of reserve asset on `FOREIGN_ASSET_RESERVE_PARA_ID`.
|
||||
pub const FOREIGN_ASSET_INNER_JUNCTION: Junction = GeneralIndex(1234567);
|
||||
|
||||
// This child parachain acts as trusted reserve for say.. USDC that can be used for fees.
|
||||
pub const USDC_RESERVE_PARA_ID: u32 = 2002;
|
||||
// Inner junction of reserve asset on `USDC_RESERVE_PARA_ID`.
|
||||
pub const USDC_INNER_JUNCTION: Junction = PalletInstance(42);
|
||||
|
||||
// This child parachain is a trusted teleporter for say.. USDT (T from Teleport :)).
|
||||
// We'll use USDT in tests that teleport fees.
|
||||
pub const USDT_PARA_ID: u32 = 2003;
|
||||
|
||||
// This child parachain is not configured as trusted reserve or teleport location for any assets.
|
||||
pub const OTHER_PARA_ID: u32 = 2009;
|
||||
|
||||
parameter_types! {
|
||||
pub const RelayLocation: MultiLocation = Here.into_location();
|
||||
pub const NativeAsset: MultiAsset = MultiAsset {
|
||||
fun: Fungible(10),
|
||||
id: Concrete(Here.into_location()),
|
||||
};
|
||||
pub const SystemParachainLocation: MultiLocation = MultiLocation {
|
||||
parents: 0,
|
||||
interior: X1(Parachain(SOME_SYSTEM_PARA))
|
||||
};
|
||||
pub const ForeignReserveLocation: MultiLocation = MultiLocation {
|
||||
parents: 0,
|
||||
interior: X1(Parachain(FOREIGN_ASSET_RESERVE_PARA_ID))
|
||||
};
|
||||
pub const ForeignAsset: MultiAsset = MultiAsset {
|
||||
fun: Fungible(10),
|
||||
id: Concrete(MultiLocation {
|
||||
parents: 0,
|
||||
interior: X2(Parachain(FOREIGN_ASSET_RESERVE_PARA_ID), FOREIGN_ASSET_INNER_JUNCTION),
|
||||
}),
|
||||
};
|
||||
pub const UsdcReserveLocation: MultiLocation = MultiLocation {
|
||||
parents: 0,
|
||||
interior: X1(Parachain(USDC_RESERVE_PARA_ID))
|
||||
};
|
||||
pub const Usdc: MultiAsset = MultiAsset {
|
||||
fun: Fungible(10),
|
||||
id: Concrete(MultiLocation {
|
||||
parents: 0,
|
||||
interior: X2(Parachain(USDC_RESERVE_PARA_ID), USDC_INNER_JUNCTION),
|
||||
}),
|
||||
};
|
||||
pub const UsdtTeleportLocation: MultiLocation = MultiLocation {
|
||||
parents: 0,
|
||||
interior: X1(Parachain(USDT_PARA_ID))
|
||||
};
|
||||
pub const Usdt: MultiAsset = MultiAsset {
|
||||
fun: Fungible(10),
|
||||
id: Concrete(MultiLocation {
|
||||
parents: 0,
|
||||
interior: X1(Parachain(USDT_PARA_ID)),
|
||||
}),
|
||||
};
|
||||
pub const AnyNetwork: Option<NetworkId> = None;
|
||||
pub UniversalLocation: InteriorMultiLocation = Here;
|
||||
pub UnitWeightCost: u64 = 1_000;
|
||||
pub CheckingAccount: AccountId = XcmPallet::check_account();
|
||||
}
|
||||
|
||||
pub type SovereignAccountOf =
|
||||
(ChildParachainConvertsVia<ParaId, AccountId>, AccountId32Aliases<AnyNetwork, AccountId>);
|
||||
pub type SovereignAccountOf = (
|
||||
ChildParachainConvertsVia<ParaId, AccountId>,
|
||||
AccountId32Aliases<AnyNetwork, AccountId>,
|
||||
HashedDescription<AccountId, DescribeAllTerminal>,
|
||||
);
|
||||
|
||||
pub type LocalAssetTransactor =
|
||||
XcmCurrencyAdapter<Balances, IsConcrete<RelayLocation>, SovereignAccountOf, AccountId, ()>;
|
||||
pub type ForeignAssetsConvertedConcreteId = MatchedConvertedConcreteId<
|
||||
MultiLocation,
|
||||
Balance,
|
||||
// Excludes relay/parent chain currency
|
||||
EverythingBut<(Equals<RelayLocation>,)>,
|
||||
Identity,
|
||||
JustTry,
|
||||
>;
|
||||
|
||||
pub type AssetTransactors = (
|
||||
XcmCurrencyAdapter<Balances, IsConcrete<RelayLocation>, SovereignAccountOf, AccountId, ()>,
|
||||
FungiblesAdapter<
|
||||
Assets,
|
||||
ForeignAssetsConvertedConcreteId,
|
||||
SovereignAccountOf,
|
||||
AccountId,
|
||||
NoChecking,
|
||||
CheckingAccount,
|
||||
>,
|
||||
);
|
||||
|
||||
type LocalOriginConverter = (
|
||||
SovereignSignedViaLocation<SovereignAccountOf, RuntimeOrigin>,
|
||||
@@ -303,7 +427,12 @@ type LocalOriginConverter = (
|
||||
parameter_types! {
|
||||
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 TrustedLocal: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into());
|
||||
pub TrustedSystemPara: (MultiAssetFilter, MultiLocation) = (NativeAsset::get().into(), SystemParachainLocation::get());
|
||||
pub TrustedUsdt: (MultiAssetFilter, MultiLocation) = (Usdt::get().into(), UsdtTeleportLocation::get());
|
||||
pub TeleportUsdtToForeign: (MultiAssetFilter, MultiLocation) = (Usdt::get().into(), ForeignReserveLocation::get());
|
||||
pub TrustedForeign: (MultiAssetFilter, MultiLocation) = (ForeignAsset::get().into(), ForeignReserveLocation::get());
|
||||
pub TrustedUsdc: (MultiAssetFilter, MultiLocation) = (Usdc::get().into(), UsdcReserveLocation::get());
|
||||
pub const MaxInstructions: u32 = 100;
|
||||
pub const MaxAssetsIntoHolding: u32 = 64;
|
||||
pub XcmFeesTargetAccount: AccountId = AccountId::new([167u8; 32]);
|
||||
@@ -323,14 +452,21 @@ pub type Barrier = (
|
||||
AllowSubscriptionsFrom<Everything>,
|
||||
);
|
||||
|
||||
pub type XcmRouter = (TestPaidForPara3000SendXcm, TestSendXcmErrX8, TestSendXcm);
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl xcm_executor::Config for XcmConfig {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type XcmSender = (TestPaidForPara3000SendXcm, TestSendXcm);
|
||||
type AssetTransactor = LocalAssetTransactor;
|
||||
type XcmSender = XcmRouter;
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = LocalOriginConverter;
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = Case<TrustedAssets>;
|
||||
type IsReserve = (Case<TrustedForeign>, Case<TrustedUsdc>);
|
||||
type IsTeleporter = (
|
||||
Case<TrustedLocal>,
|
||||
Case<TrustedSystemPara>,
|
||||
Case<TrustedUsdt>,
|
||||
Case<TeleportUsdtToForeign>,
|
||||
);
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = Barrier;
|
||||
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, MaxInstructions>;
|
||||
@@ -360,15 +496,10 @@ parameter_types! {
|
||||
pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 3;
|
||||
}
|
||||
|
||||
#[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 = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
type XcmRouter = (TestSendXcmErrX8, TestPaidForPara3000SendXcm, TestSendXcm);
|
||||
type XcmRouter = XcmRouter;
|
||||
type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
type XcmExecuteFilter = Everything;
|
||||
type XcmExecutor = XcmExecutor<XcmConfig>;
|
||||
@@ -380,6 +511,7 @@ impl pallet_xcm::Config for Test {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = AdvertisedXcmVersion;
|
||||
type AdminOrigin = EnsureRoot<AccountId>;
|
||||
type TrustedLockers = ();
|
||||
type SovereignAccountOf = AccountId32Aliases<(), AccountId32>;
|
||||
type Currency = Balances;
|
||||
@@ -388,9 +520,6 @@ impl pallet_xcm::Config for Test {
|
||||
type MaxRemoteLockConsumers = frame_support::traits::ConstU32<0>;
|
||||
type RemoteLockConsumerIdentifier = ();
|
||||
type WeightInfo = TestWeightInfo;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type ReachableDest = ReachableDest;
|
||||
type AdminOrigin = EnsureRoot<AccountId>;
|
||||
}
|
||||
|
||||
impl origin::Config for Test {}
|
||||
@@ -401,6 +530,24 @@ impl pallet_test_notifier::Config for Test {
|
||||
type RuntimeCall = RuntimeCall;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl super::benchmarking::Config for Test {
|
||||
fn reachable_dest() -> Option<MultiLocation> {
|
||||
Some(Parachain(1000).into())
|
||||
}
|
||||
|
||||
fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> {
|
||||
Some((NativeAsset::get(), SystemParachainLocation::get()))
|
||||
}
|
||||
|
||||
fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> {
|
||||
Some((
|
||||
MultiAsset { fun: Fungible(10), id: Concrete(Here.into_location()) },
|
||||
Parachain(OTHER_PARA_ID).into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn last_event() -> RuntimeEvent {
|
||||
System::events().pop().expect("RuntimeEvent expected").event
|
||||
}
|
||||
@@ -416,10 +563,10 @@ pub(crate) fn buy_execution<C>(fees: impl Into<MultiAsset>) -> Instruction<C> {
|
||||
|
||||
pub(crate) fn buy_limited_execution<C>(
|
||||
fees: impl Into<MultiAsset>,
|
||||
weight: Weight,
|
||||
weight_limit: WeightLimit,
|
||||
) -> Instruction<C> {
|
||||
use xcm::latest::prelude::*;
|
||||
BuyExecution { fees: fees.into(), weight_limit: Limited(weight) }
|
||||
BuyExecution { fees: fees.into(), weight_limit }
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext_with_balances(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
mod assets_transfer;
|
||||
|
||||
use crate::{
|
||||
mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedMultiLocation, Queries,
|
||||
QueryStatus, VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers,
|
||||
@@ -35,15 +39,15 @@ use xcm_executor::{
|
||||
|
||||
const ALICE: AccountId = AccountId::new([0u8; 32]);
|
||||
const BOB: AccountId = AccountId::new([1u8; 32]);
|
||||
const PARA_ID: u32 = 2000;
|
||||
const INITIAL_BALANCE: u128 = 100;
|
||||
const SEND_AMOUNT: u128 = 10;
|
||||
const FEE_AMOUNT: u128 = 2;
|
||||
|
||||
#[test]
|
||||
fn report_outcome_notify_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
let mut message =
|
||||
@@ -56,7 +60,7 @@ fn report_outcome_notify_works() {
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
XcmPallet::report_outcome_notify(
|
||||
&mut message,
|
||||
Parachain(PARA_ID).into_location(),
|
||||
Parachain(OTHER_PARA_ID).into_location(),
|
||||
notify,
|
||||
100,
|
||||
)
|
||||
@@ -74,8 +78,8 @@ fn report_outcome_notify_works() {
|
||||
);
|
||||
let querier: MultiLocation = Here.into();
|
||||
let status = QueryStatus::Pending {
|
||||
responder: MultiLocation::from(Parachain(PARA_ID)).into(),
|
||||
maybe_notify: Some((4, 2)),
|
||||
responder: MultiLocation::from(Parachain(OTHER_PARA_ID)).into(),
|
||||
maybe_notify: Some((5, 2)),
|
||||
timeout: 100,
|
||||
maybe_match_querier: Some(querier.into()),
|
||||
};
|
||||
@@ -89,7 +93,7 @@ fn report_outcome_notify_works() {
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<XcmConfig>::execute_xcm(
|
||||
Parachain(PARA_ID),
|
||||
Parachain(OTHER_PARA_ID),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(1_000_000_000, 1_000_000_000),
|
||||
@@ -99,13 +103,13 @@ fn report_outcome_notify_works() {
|
||||
last_events(2),
|
||||
vec![
|
||||
RuntimeEvent::TestNotifier(pallet_test_notifier::Event::ResponseReceived(
|
||||
Parachain(PARA_ID).into(),
|
||||
Parachain(OTHER_PARA_ID).into(),
|
||||
0,
|
||||
Response::ExecutionResult(None),
|
||||
)),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Notified {
|
||||
query_id: 0,
|
||||
pallet_index: 4,
|
||||
pallet_index: 5,
|
||||
call_index: 2
|
||||
}),
|
||||
]
|
||||
@@ -118,13 +122,14 @@ fn report_outcome_notify_works() {
|
||||
fn report_outcome_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
let mut message =
|
||||
Xcm(vec![TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender }]);
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
XcmPallet::report_outcome(&mut message, Parachain(PARA_ID).into_location(), 100).unwrap();
|
||||
XcmPallet::report_outcome(&mut message, Parachain(OTHER_PARA_ID).into_location(), 100)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
message,
|
||||
Xcm(vec![
|
||||
@@ -138,7 +143,7 @@ fn report_outcome_works() {
|
||||
);
|
||||
let querier: MultiLocation = Here.into();
|
||||
let status = QueryStatus::Pending {
|
||||
responder: MultiLocation::from(Parachain(PARA_ID)).into(),
|
||||
responder: MultiLocation::from(Parachain(OTHER_PARA_ID)).into(),
|
||||
maybe_notify: None,
|
||||
timeout: 100,
|
||||
maybe_match_querier: Some(querier.into()),
|
||||
@@ -153,7 +158,7 @@ fn report_outcome_works() {
|
||||
}]);
|
||||
let hash = fake_message_hash(&message);
|
||||
let r = XcmExecutor::<XcmConfig>::execute_xcm(
|
||||
Parachain(PARA_ID),
|
||||
Parachain(OTHER_PARA_ID),
|
||||
message,
|
||||
hash,
|
||||
Weight::from_parts(1_000_000_000, 1_000_000_000),
|
||||
@@ -177,7 +182,7 @@ fn report_outcome_works() {
|
||||
fn custom_querier_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let querier: MultiLocation =
|
||||
@@ -281,7 +286,7 @@ fn custom_querier_works() {
|
||||
fn send_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let sender: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
@@ -325,7 +330,7 @@ fn send_works() {
|
||||
fn send_fails_when_xcm_router_blocks() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let sender: MultiLocation =
|
||||
@@ -346,344 +351,6 @@ fn send_fails_when_xcm_router_blocks() {
|
||||
});
|
||||
}
|
||||
|
||||
/// Test `teleport_assets`
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased as a result of execution of
|
||||
/// local effects.
|
||||
#[test]
|
||||
fn teleport_assets_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let weight = BaseXcmWeight::get() * 3;
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
|
||||
let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into();
|
||||
assert_ok!(XcmPallet::teleport_assets(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(RelayLocation::get().into()),
|
||||
Box::new(dest.into()),
|
||||
Box::new((Here, SEND_AMOUNT).into()),
|
||||
0,
|
||||
));
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
RelayLocation::get().into(),
|
||||
Xcm(vec![
|
||||
ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_execution((Here, SEND_AMOUNT)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1);
|
||||
let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap();
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test `limited_teleport_assets`
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased as a result of execution of
|
||||
/// local effects.
|
||||
#[test]
|
||||
fn limited_teleport_assets_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let weight = BaseXcmWeight::get() * 3;
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
|
||||
let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into();
|
||||
assert_ok!(XcmPallet::limited_teleport_assets(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(RelayLocation::get().into()),
|
||||
Box::new(dest.into()),
|
||||
Box::new((Here, SEND_AMOUNT).into()),
|
||||
0,
|
||||
WeightLimit::Limited(Weight::from_parts(5000, 5000)),
|
||||
));
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
RelayLocation::get().into(),
|
||||
Xcm(vec![
|
||||
ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_limited_execution((Here, SEND_AMOUNT), Weight::from_parts(5000, 5000)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1);
|
||||
let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap();
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test `limited_teleport_assets` with unlimited weight
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased as a result of execution of
|
||||
/// local effects.
|
||||
#[test]
|
||||
fn unlimited_teleport_assets_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let weight = BaseXcmWeight::get() * 3;
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
|
||||
let dest: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into();
|
||||
assert_ok!(XcmPallet::limited_teleport_assets(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(RelayLocation::get().into()),
|
||||
Box::new(dest.into()),
|
||||
Box::new((Here, SEND_AMOUNT).into()),
|
||||
0,
|
||||
WeightLimit::Unlimited,
|
||||
));
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
RelayLocation::get().into(),
|
||||
Xcm(vec![
|
||||
ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_execution((Here, SEND_AMOUNT)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test `reserve_transfer_assets`
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased and the beneficiary's balance
|
||||
/// is increased. Verifies the correct message is sent and event is emitted.
|
||||
#[test]
|
||||
fn reserve_transfer_assets_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let weight = BaseXcmWeight::get() * 2;
|
||||
let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
|
||||
assert_ok!(XcmPallet::reserve_transfer_assets(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(Parachain(PARA_ID).into()),
|
||||
Box::new(dest.into()),
|
||||
Box::new((Here, SEND_AMOUNT).into()),
|
||||
0,
|
||||
));
|
||||
// Alice spent amount
|
||||
assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT);
|
||||
// Destination account (parachain account) has amount
|
||||
let para_acc: AccountId = ParaId::from(PARA_ID).into_account_truncating();
|
||||
assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
Parachain(PARA_ID).into(),
|
||||
Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_execution((Parent, SEND_AMOUNT)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1);
|
||||
let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap();
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test `reserve_transfer_assets_with_paid_router_works`
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased and the beneficiary's balance
|
||||
/// is increased. Verifies the correct message is sent and event is emitted.
|
||||
/// Verifies that XCM router fees (`SendXcm::validate` -> `MultiAssets`) are withdrawn from correct
|
||||
/// user account and deposited to a correct target account (`XcmFeesTargetAccount`).
|
||||
#[test]
|
||||
fn reserve_transfer_assets_with_paid_router_works() {
|
||||
let user_account = AccountId::from(XCM_FEES_NOT_WAIVED_USER_ACCOUNT);
|
||||
let paid_para_id = Para3000::get();
|
||||
let balances = vec![
|
||||
(user_account.clone(), INITIAL_BALANCE),
|
||||
(ParaId::from(paid_para_id).into_account_truncating(), INITIAL_BALANCE),
|
||||
(XcmFeesTargetAccount::get(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let xcm_router_fee_amount = Para3000PaymentAmount::get();
|
||||
let weight = BaseXcmWeight::get() * 2;
|
||||
let dest: MultiLocation =
|
||||
Junction::AccountId32 { network: None, id: user_account.clone().into() }.into();
|
||||
assert_eq!(Balances::total_balance(&user_account), INITIAL_BALANCE);
|
||||
assert_ok!(XcmPallet::reserve_transfer_assets(
|
||||
RuntimeOrigin::signed(user_account.clone()),
|
||||
Box::new(Parachain(paid_para_id).into()),
|
||||
Box::new(dest.into()),
|
||||
Box::new((Here, SEND_AMOUNT).into()),
|
||||
0,
|
||||
));
|
||||
// check event
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
|
||||
// XCM_FEES_NOT_WAIVED_USER_ACCOUNT spent amount
|
||||
assert_eq!(
|
||||
Balances::free_balance(user_account),
|
||||
INITIAL_BALANCE - SEND_AMOUNT - xcm_router_fee_amount
|
||||
);
|
||||
// Destination account (parachain account) has amount
|
||||
let para_acc: AccountId = ParaId::from(paid_para_id).into_account_truncating();
|
||||
assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT);
|
||||
// XcmFeesTargetAccount where should lend xcm_router_fee_amount
|
||||
assert_eq!(
|
||||
Balances::free_balance(XcmFeesTargetAccount::get()),
|
||||
INITIAL_BALANCE + xcm_router_fee_amount
|
||||
);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
Parachain(paid_para_id).into(),
|
||||
Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_execution((Parent, SEND_AMOUNT)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1);
|
||||
let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap();
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test `limited_reserve_transfer_assets`
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased and the beneficiary's balance
|
||||
/// is increased. Verifies the correct message is sent and event is emitted.
|
||||
#[test]
|
||||
fn limited_reserve_transfer_assets_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let weight = BaseXcmWeight::get() * 2;
|
||||
let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
|
||||
assert_ok!(XcmPallet::limited_reserve_transfer_assets(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(Parachain(PARA_ID).into()),
|
||||
Box::new(dest.into()),
|
||||
Box::new((Here, SEND_AMOUNT).into()),
|
||||
0,
|
||||
WeightLimit::Limited(Weight::from_parts(5000, 5000)),
|
||||
));
|
||||
// Alice spent amount
|
||||
assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT);
|
||||
// Destination account (parachain account) has amount
|
||||
let para_acc: AccountId = ParaId::from(PARA_ID).into_account_truncating();
|
||||
assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
Parachain(PARA_ID).into(),
|
||||
Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_limited_execution((Parent, SEND_AMOUNT), Weight::from_parts(5000, 5000)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1);
|
||||
let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap();
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test `limited_reserve_transfer_assets` with unlimited weight purchasing
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased and the beneficiary's balance
|
||||
/// is increased. Verifies the correct message is sent and event is emitted.
|
||||
#[test]
|
||||
fn unlimited_reserve_transfer_assets_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let weight = BaseXcmWeight::get() * 2;
|
||||
let dest: MultiLocation = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
|
||||
assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
|
||||
assert_ok!(XcmPallet::limited_reserve_transfer_assets(
|
||||
RuntimeOrigin::signed(ALICE),
|
||||
Box::new(Parachain(PARA_ID).into()),
|
||||
Box::new(dest.into()),
|
||||
Box::new((Here, SEND_AMOUNT).into()),
|
||||
0,
|
||||
WeightLimit::Unlimited,
|
||||
));
|
||||
// Alice spent amount
|
||||
assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT);
|
||||
// Destination account (parachain account) has amount
|
||||
let para_acc: AccountId = ParaId::from(PARA_ID).into_account_truncating();
|
||||
assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT);
|
||||
assert_eq!(
|
||||
sent_xcm(),
|
||||
vec![(
|
||||
Parachain(PARA_ID).into(),
|
||||
Xcm(vec![
|
||||
ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
|
||||
ClearOrigin,
|
||||
buy_execution((Parent, SEND_AMOUNT)),
|
||||
DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
|
||||
]),
|
||||
)]
|
||||
);
|
||||
assert_eq!(
|
||||
last_event(),
|
||||
RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Test local execution of XCM
|
||||
///
|
||||
/// Asserts that the sender's balance is decreased and the beneficiary's balance
|
||||
@@ -692,7 +359,7 @@ fn unlimited_reserve_transfer_assets_works() {
|
||||
fn execute_withdraw_to_deposit_works() {
|
||||
let balances = vec![
|
||||
(ALICE, INITIAL_BALANCE),
|
||||
(ParaId::from(PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
(ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE),
|
||||
];
|
||||
new_test_ext_with_balances(balances).execute_with(|| {
|
||||
let weight = BaseXcmWeight::get() * 3;
|
||||
Reference in New Issue
Block a user