Closed acatangiu closed 7 months ago
Looking at the xtokens implementation, I realize for ThirdPartyReserve
scenario we need to send XCM programs described above twice if transferred asset(s) reserve is different than fee-asset reserve, once to the reserve1 then dest, second to fee-reserve then dest.
Will actually try to make it more general-purpose and send as many XCM programs as required to as many reserves as are required to transfer all assets.
Some current (possibly dumb) questions around how teleports and reserve-based transfers work together:
Both native-asset and foreign-assets are allowed to be teleported to AssetHub, where the assets are defined as (from AssetHub context):
AssetId: Concrete(Multilocation { parents: 1, interior: Junctions::Here})
AssetId: Concrete(Multilocation { parents: 1, interior: X1(Parachain(42))})
Let's look at the more generic FAP42
example:
Alice
on Para42
teleports 100 FAP42
to AssetHub
,Alice
later wants to reserve-transfer FAP42
from AssetHub
to Para84
FAP42
's reserve is Para42
so we're in "ThirdPartyReserve" scenario, with InitiateReserveWithdraw
being executed locally with reserve set to Para42
,
vec![WithdrawAsset(assets), ClearOrigin]
to Para42
with AH as origin
,Para42
, WithdrawAsset
will fail because FAP42
balance of SA-of-AH-on-Para42 is zero, FAP42
s have been teleported to AH, not reserve-transferred.So it becomes obvious that pallet-xcm
needs to know which assets are teleportable-where and which have to be reserve-based, and reserve_transfer_assets()
has to use either InitiateTeleport
or InitiateReserveWithdraw
accordingly
The existing IsTeleporter is configured in xcm_executor::Config
but not available to pallet-xcm
.
pallet_xcm
as well, or re-export it from executor to pallet-xcm? smth like:
Note: pallet-xcm already has XcmTeleportFilter and XcmReserveTransferFilter associated types, but afaict these filter what assets
origin
is allowed to operate on. We need a filter to say for asset
and target
combo, do we teleport or do we reserve-transfer?
Transfers will check IsReserve/IsTeleport to reason whether to teleport or reserve-transfer the asset to the destination.
Transfers will check IsReserve/IsTeleport to reason whether to teleport or reserve-transfer the asset to the destination.
Proposed logic:
if IsTeleporter::contains(asset, dest) {
// we trust destination for teleporting asset
return TransferType::Teleport
} else if IsReserve::contains(asset, dest) {
// we trust destination as asset reserve location
return TransferType::DestinationReserve
}
// try to determine reserve location based on asset id/location
let asset_location = match asset.id {
Concrete(location) => location.chain_location(),
_ => fail!(),
};
if asset_location == MultiLocation::here() || IsTeleporter::contains(asset, &asset_location) {
// local asset, or remote location that allows local teleports => local reserve
TransferType::LocalReserve
} else if IsReserve::contains(asset, &asset_location) {
// remote location that is recognized as reserve location for asset
TransferType::RemoteReserve(asset_location)
} else {
// remote location that is not configured either as teleporter or reserve => cannot
// determine asset reserve
Error::<T>::UnknownReserve
}
We need a filter to say for
asset
andtarget
combo, do we teleport or do we reserve-transfer?
In the very first version of asset transfer with custom extrinsic, exactly for this I had trait ResolveAssetTransferKind:
pub trait ResolveAssetTransferKind {
fn resolve(asset: &MultiAsset, target_location: &MultiLocation) -> AssetTransferKind;
}
which was part of pallet's Config:
/// Transfer kind resolver for `asset` to `target_location`.
type AssetTransferKindResolver: ResolveAssetTransferKind;
My basic idea was to move IFs like IsReserve::contains
or IsTeleporter::contains
out of extrinsic implementation to be more generic and also to give possibility to create runtime dependent logic for resolving supported transfer kinds or maybe disable all like type AssetTransferKindResolver = ()
or type AssetTransferKindResolver = Nothing
. And here was some basic implementation of ResolveAssetTransferKind.
The existing IsTeleporter is configured in xcm_executor::Config but not available to pallet-xcm.
type XcmExecutor: ExecuteXcm<
::RuntimeCall> + AssetFilters;
So this trait ResolveAssetTransferKind
is just an idea how to separate Proposed logic above out of pallet_xcm
and do not make it ExecuteXcm
-dependent. Also with this solution, we dont need IsTeleporter
/ IsReserve
in pallet_xcm
. And probably generic pallet_xcm
should not do unnecessary assumptions about type XcmExecutor: ExecuteXcm
.
For example, lets say that any other team is working on their own implementation of ExecuteXcm
and they dont need reserve-transfer but they need pallet_xcm, so with + AssetFilters
we will enforce them to implement dummy AssetFilters
. So maybe something like type AssetTransferKindResolver = ()
is better for them.
The existing IsTeleporter is configured in xcm_executor::Config but not available to pallet-xcm.
type XcmExecutor: ExecuteXcm<::RuntimeCall> + AssetFilters;
So this
trait ResolveAssetTransferKind
is just an idea how to separate Proposed logic above out ofpallet_xcm
and do not make itExecuteXcm
-dependent. Also with this solution, we dont needIsTeleporter
/IsReserve
inpallet_xcm
. And probably genericpallet_xcm
should not do unnecessary assumptions abouttype XcmExecutor: ExecuteXcm
.For example, lets say that any other team is working on their own implementation of
ExecuteXcm
and they dont need reserve-transfer but they need pallet_xcm, so with+ AssetFilters
we will enforce them to implement dummyAssetFilters
. So maybe something liketype AssetTransferKindResolver = ()
is better for them.
I thought about this, but ultimately decided against it because moving it to runtime configuration means basically duplicating IsTeleporter
and IsTrusted
at the runtime xcm_config level -> duplicated code + chances for the two (xcm-executor::config & pallet-xcm::config) to become out of sync.
The used trait AssetTransferFilter
is implemented on XcmExecutor. Using any custom executor allows you to also implement custom AssetTransferFilter
so if your executor doesn't care about transfers, then AssetTransferFilter = ()
.
But this way, all runtimes that are using XcmExecutor, automatically get AssetTransferFilter
which is already configured to match the executor's capabilities (config/knowledge of teleporters and reserves).
We could move this transfer type resolver from pallet-xcm
into xcm-executor
and expose just resolve_transfer_type(asset, dest)
through executor trait AssetTransferFilter
impl.
Either way, I still believe this should be tied to the executor - it ultimately holds the logic for handling all transfer-related XCM instructions and already uses IsTeleport
and IsReserve
to reason about what works how. So it makes sense to expose this stuff, make it clear what the executor's requirements and capabilities are instead of leaving it to runtime developers to manually try to match them when configuring XCM transfers pallets (like pallet-xcm).
Context
When reserve transferring asset
A
from chainX
to chainY
, the reserve location forA
can fall in one of three scenarios:LocalReserve
: reserve is on source chainX
(Here
),DestinationReserve
: reserve is on target/destination chainY
,ThirdPartyReserve
: reserve is on third-party chainZ
.Problem
Currently,
pallet-xcm::reserve_transfer_assets()
extrinsic only covers scenario1
above - even worse, it automatically assumes it is being called under scenario 1, without doing all the proper checks.This is very limiting in terms of reserve-based transfers supported by
pallet-xcm
.Solution proposal
Enhance
pallet-xcm::reserve_transfer_assets()
with support for all scenarios above - it should figure out by itself which scenario it is operating under based on input parameters (ideally without changing function/call API).Once it figures out which scenario it is operating under, implement the following:
LocalReserve
Reserve is local chain
reserve_chain == source_chain
.Same as current implementation:
DestinationReserve
Implementation when
reserve_chain == destination_chain
:ThirdPartyReserve
Implementation when
reserve_chain != source_chain && reserve_chain != destination_chain
: