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:
Adrian Catangiu
2023-11-13 17:16:55 +02:00
committed by GitHub
parent 29654a4d71
commit 18257373b3
80 changed files with 3679 additions and 1466 deletions
+7 -1
View File
@@ -32,7 +32,7 @@ pub mod traits;
use traits::{
validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin,
DropAssets, Enact, ExportXcm, FeeManager, FeeReason, OnResponse, Properties, ShouldExecute,
TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader,
TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader, XcmAssetTransfers,
};
mod assets;
@@ -254,6 +254,12 @@ impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Con
}
}
impl<Config: config::Config> XcmAssetTransfers for XcmExecutor<Config> {
type IsReserve = Config::IsReserve;
type IsTeleporter = Config::IsTeleporter;
type AssetTransactor = Config::AssetTransactor;
}
#[derive(Debug)]
pub struct ExecutorError {
pub index: u32,
@@ -0,0 +1,90 @@
// 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 crate::traits::TransactAsset;
use frame_support::traits::ContainsPair;
use scale_info::TypeInfo;
use sp_runtime::codec::{Decode, Encode};
use xcm::prelude::*;
/// Errors related to determining asset transfer support.
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)]
pub enum Error {
/// Invalid non-concrete asset.
NotConcrete,
/// Reserve chain could not be determined for assets.
UnknownReserve,
}
/// Specify which type of asset transfer is required for a particular `(asset, dest)` combination.
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum TransferType {
/// should teleport `asset` to `dest`
Teleport,
/// should reserve-transfer `asset` to `dest`, using local chain as reserve
LocalReserve,
/// should reserve-transfer `asset` to `dest`, using `dest` as reserve
DestinationReserve,
/// should reserve-transfer `asset` to `dest`, using remote chain `MultiLocation` as reserve
RemoteReserve(MultiLocation),
}
/// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve`
/// configurations.
pub trait XcmAssetTransfers {
/// Combinations of (Asset, Location) pairs which we trust as reserves. Meaning
/// reserve-based-transfers are to be used for assets matching this filter.
type IsReserve: ContainsPair<MultiAsset, MultiLocation>;
/// Combinations of (Asset, Location) pairs which we trust as teleporters. Meaning teleports are
/// to be used for assets matching this filter.
type IsTeleporter: ContainsPair<MultiAsset, MultiLocation>;
/// How to withdraw and deposit an asset.
type AssetTransactor: TransactAsset;
/// Determine transfer type to be used for transferring `asset` from local chain to `dest`.
fn determine_for(asset: &MultiAsset, dest: &MultiLocation) -> Result<TransferType, Error> {
if Self::IsTeleporter::contains(asset, dest) {
// we trust destination for teleporting asset
return Ok(TransferType::Teleport)
} else if Self::IsReserve::contains(asset, dest) {
// we trust destination as asset reserve location
return Ok(TransferType::DestinationReserve)
}
// try to determine reserve location based on asset id/location
let asset_location = match asset.id {
Concrete(location) => Ok(location.chain_location()),
_ => Err(Error::NotConcrete),
}?;
if asset_location == MultiLocation::here() ||
Self::IsTeleporter::contains(asset, &asset_location)
{
// if the asset is local, then it's a local reserve
// it's also a local reserve if the asset's location is not `here` but it's a location
// where it can be teleported to `here` => local reserve
Ok(TransferType::LocalReserve)
} else if Self::IsReserve::contains(asset, &asset_location) {
// remote location that is recognized as reserve location for asset
Ok(TransferType::RemoteReserve(asset_location))
} else {
// remote location that is not configured either as teleporter or reserve => cannot
// determine asset reserve
Err(Error::UnknownReserve)
}
}
}
+4 -2
View File
@@ -20,10 +20,12 @@ mod conversion;
pub use conversion::{CallDispatcher, ConvertLocation, ConvertOrigin, WithOriginFilter};
mod drop_assets;
pub use drop_assets::{ClaimAssets, DropAssets};
mod asset_lock;
pub use asset_lock::{AssetLock, Enact, LockError};
mod asset_exchange;
pub use asset_exchange::AssetExchange;
mod asset_lock;
pub use asset_lock::{AssetLock, Enact, LockError};
mod asset_transfer;
pub use asset_transfer::{Error as AssetTransferError, TransferType, XcmAssetTransfers};
mod export;
pub use export::{export_xcm, validate_export, ExportXcm};
mod fee_manager;