mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 22:11: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:
@@ -434,7 +434,7 @@ parameter_types! {
|
||||
// pub type AssetsForceOrigin =
|
||||
// EnsureOneOf<EnsureRoot<AccountId>, EnsureXcm<IsMajorityOfBody<KsmLocation, ExecutiveBody>>>;
|
||||
|
||||
impl pallet_assets::Config for Runtime {
|
||||
impl pallet_assets::Config<pallet_assets::Instance1> for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = Balance;
|
||||
type AssetId = AssetId;
|
||||
@@ -577,7 +577,12 @@ impl pallet_asset_tx_payment::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Fungibles = Assets;
|
||||
type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter<
|
||||
pallet_assets::BalanceToAssetBalance<Balances, Runtime, ConvertInto>,
|
||||
pallet_assets::BalanceToAssetBalance<
|
||||
Balances,
|
||||
Runtime,
|
||||
ConvertInto,
|
||||
pallet_assets::Instance1,
|
||||
>,
|
||||
AssetsToBlockAuthor<Runtime>,
|
||||
>;
|
||||
}
|
||||
@@ -619,7 +624,7 @@ construct_runtime!(
|
||||
MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event<T>} = 34,
|
||||
|
||||
// The main stage.
|
||||
Assets: pallet_assets::{Pallet, Call, Storage, Event<T>} = 50,
|
||||
Assets: pallet_assets::<Instance1>::{Pallet, Call, Storage, Event<T>} = 50,
|
||||
|
||||
Sudo: pallet_sudo::{Pallet, Call, Storage, Event<T>, Config<T>} = 255,
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ use frame_support::{
|
||||
};
|
||||
use frame_system::EnsureRoot;
|
||||
use pallet_asset_tx_payment::HandleCredit;
|
||||
use pallet_assets::Instance1;
|
||||
use pallet_xcm::XcmPassthrough;
|
||||
use polkadot_parachain_primitives::primitives::Sibling;
|
||||
use polkadot_runtime_common::impls::ToAuthor;
|
||||
@@ -48,9 +49,10 @@ use xcm_builder::{
|
||||
AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex,
|
||||
ConvertedConcreteId, CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry,
|
||||
EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset,
|
||||
ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
|
||||
SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
|
||||
TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic,
|
||||
ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative,
|
||||
SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
|
||||
SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents,
|
||||
WithComputedOrigin, WithUniqueTopic,
|
||||
};
|
||||
use xcm_executor::{traits::JustTry, XcmExecutor};
|
||||
|
||||
@@ -126,6 +128,9 @@ pub type XcmOriginToTransactDispatchOrigin = (
|
||||
// Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
|
||||
// recognized.
|
||||
SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>,
|
||||
// Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
|
||||
// transaction from the Root origin.
|
||||
ParentAsSuperuser<RuntimeOrigin>,
|
||||
// Native signed account converter; this just converts an `AccountId32` origin into a normal
|
||||
// `RuntimeOrigin::Signed` origin of the same 32-byte value.
|
||||
SignedAccountId32AsNative<RelayNetwork, RuntimeOrigin>,
|
||||
@@ -182,14 +187,25 @@ pub type Barrier = TrailingSetTopicAsId<
|
||||
/// Type alias to conveniently refer to `frame_system`'s `Config::AccountId`.
|
||||
pub type AccountIdOf<R> = <R as frame_system::Config>::AccountId;
|
||||
|
||||
/// Asset filter that allows all assets from a certain location.
|
||||
/// Asset filter that allows all assets from a certain location matching asset id.
|
||||
pub struct AssetsFrom<T>(PhantomData<T>);
|
||||
impl<T: Get<MultiLocation>> ContainsPair<MultiAsset, MultiLocation> for AssetsFrom<T> {
|
||||
fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
let loc = T::get();
|
||||
&loc == origin &&
|
||||
matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) }
|
||||
if asset_loc.match_and_split(&loc).is_some())
|
||||
if asset_loc.starts_with(&loc))
|
||||
}
|
||||
}
|
||||
|
||||
/// Asset filter that allows native/relay asset if coming from a certain location.
|
||||
pub struct NativeAssetFrom<T>(PhantomData<T>);
|
||||
impl<T: Get<MultiLocation>> ContainsPair<MultiAsset, MultiLocation> for NativeAssetFrom<T> {
|
||||
fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
let loc = T::get();
|
||||
&loc == origin &&
|
||||
matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) }
|
||||
if *asset_loc == MultiLocation::from(Parent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,56 +224,19 @@ where
|
||||
/// A `HandleCredit` implementation that naively transfers the fees to the block author.
|
||||
/// Will drop and burn the assets in case the transfer fails.
|
||||
pub struct AssetsToBlockAuthor<R>(PhantomData<R>);
|
||||
impl<R> HandleCredit<AccountIdOf<R>, pallet_assets::Pallet<R>> for AssetsToBlockAuthor<R>
|
||||
impl<R> HandleCredit<AccountIdOf<R>, pallet_assets::Pallet<R, Instance1>> for AssetsToBlockAuthor<R>
|
||||
where
|
||||
R: pallet_authorship::Config + pallet_assets::Config,
|
||||
R: pallet_authorship::Config + pallet_assets::Config<Instance1>,
|
||||
AccountIdOf<R>: From<polkadot_primitives::AccountId> + Into<polkadot_primitives::AccountId>,
|
||||
{
|
||||
fn handle_credit(credit: Credit<AccountIdOf<R>, pallet_assets::Pallet<R>>) {
|
||||
fn handle_credit(credit: Credit<AccountIdOf<R>, pallet_assets::Pallet<R, Instance1>>) {
|
||||
if let Some(author) = pallet_authorship::Pallet::<R>::author() {
|
||||
// In case of error: Will drop the result triggering the `OnDrop` of the imbalance.
|
||||
let _ = pallet_assets::Pallet::<R>::resolve(&author, credit);
|
||||
let _ = pallet_assets::Pallet::<R, Instance1>::resolve(&author, credit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Reserve {
|
||||
/// Returns assets reserve location.
|
||||
fn reserve(&self) -> Option<MultiLocation>;
|
||||
}
|
||||
|
||||
// Takes the chain part of a MultiAsset
|
||||
impl Reserve for MultiAsset {
|
||||
fn reserve(&self) -> Option<MultiLocation> {
|
||||
if let AssetId::Concrete(location) = self.id {
|
||||
let first_interior = location.first_interior();
|
||||
let parents = location.parent_count();
|
||||
match (parents, first_interior) {
|
||||
(0, Some(Parachain(id))) => Some(MultiLocation::new(0, X1(Parachain(*id)))),
|
||||
(1, Some(Parachain(id))) => Some(MultiLocation::new(1, X1(Parachain(*id)))),
|
||||
(1, _) => Some(MultiLocation::parent()),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `FilterAssetLocation` implementation. Filters multi native assets whose
|
||||
/// reserve is same with `origin`.
|
||||
pub struct MultiNativeAsset;
|
||||
impl ContainsPair<MultiAsset, MultiLocation> for MultiNativeAsset {
|
||||
fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool {
|
||||
if let Some(ref reserve) = asset.reserve() {
|
||||
if reserve == origin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
/// The location that this chain recognizes as the Relay network's Asset Hub.
|
||||
pub SystemAssetHubLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1000)));
|
||||
@@ -268,7 +247,8 @@ parameter_types! {
|
||||
pub CheckingAccount: AccountId = PolkadotXcm::check_account();
|
||||
}
|
||||
|
||||
pub type Reserves = (NativeAsset, AssetsFrom<SystemAssetHubLocation>);
|
||||
pub type Reserves =
|
||||
(NativeAsset, AssetsFrom<SystemAssetHubLocation>, NativeAssetFrom<SystemAssetHubLocation>);
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl xcm_executor::Config for XcmConfig {
|
||||
@@ -277,7 +257,8 @@ impl xcm_executor::Config for XcmConfig {
|
||||
// How to withdraw and deposit an asset.
|
||||
type AssetTransactor = AssetTransactors;
|
||||
type OriginConverter = XcmOriginToTransactDispatchOrigin;
|
||||
type IsReserve = MultiNativeAsset; // TODO: maybe needed to be replaced by Reserves
|
||||
type IsReserve = Reserves;
|
||||
// no teleport trust established with other chains
|
||||
type IsTeleporter = NativeAsset;
|
||||
type UniversalLocation = UniversalLocation;
|
||||
type Barrier = Barrier;
|
||||
@@ -312,11 +293,6 @@ pub type XcmRouter = WithUniqueTopic<(
|
||||
XcmpQueue,
|
||||
)>;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
parameter_types! {
|
||||
pub ReachableDest: Option<MultiLocation> = Some(Parent.into());
|
||||
}
|
||||
|
||||
impl pallet_xcm::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SendXcmOrigin = EnsureXcmOrigin<RuntimeOrigin, LocalOriginToLocation>;
|
||||
@@ -342,8 +318,6 @@ impl pallet_xcm::Config for Runtime {
|
||||
type SovereignAccountOf = LocationToAccountId;
|
||||
type MaxLockers = ConstU32<8>;
|
||||
type WeightInfo = pallet_xcm::TestWeightInfo;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type ReachableDest = ReachableDest;
|
||||
type AdminOrigin = EnsureRoot<AccountId>;
|
||||
type MaxRemoteLockConsumers = ConstU32<0>;
|
||||
type RemoteLockConsumerIdentifier = ();
|
||||
|
||||
Reference in New Issue
Block a user