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
+66
View File
@@ -444,6 +444,21 @@ impl MultiLocation {
}
}
}
/// Return the MultiLocation subsection identifying the chain that `self` points to.
pub fn chain_location(&self) -> MultiLocation {
let mut clone = *self;
// start popping junctions until we reach chain identifier
while let Some(j) = clone.last() {
if matches!(j, Junction::Parachain(_) | Junction::GlobalConsensus(_)) {
// return chain subsection
return clone
} else {
(clone, _) = clone.split_last_interior();
}
}
MultiLocation::new(clone.parents, Junctions::Here)
}
}
impl TryFrom<OldMultiLocation> for MultiLocation {
@@ -674,6 +689,57 @@ mod tests {
assert_eq!(iter.next_back(), None);
}
#[test]
fn chain_location_works() {
// Relay-chain or parachain context pointing to local resource,
let relay_to_local = MultiLocation::new(0, (PalletInstance(42), GeneralIndex(42)));
assert_eq!(relay_to_local.chain_location(), MultiLocation::here());
// Relay-chain context pointing to child parachain,
let relay_to_child =
MultiLocation::new(0, (Parachain(42), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(0, Parachain(42));
assert_eq!(relay_to_child.chain_location(), expected);
// Relay-chain context pointing to different consensus relay,
let relay_to_remote_relay =
MultiLocation::new(1, (GlobalConsensus(Kusama), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(1, GlobalConsensus(Kusama));
assert_eq!(relay_to_remote_relay.chain_location(), expected);
// Relay-chain context pointing to different consensus parachain,
let relay_to_remote_para = MultiLocation::new(
1,
(GlobalConsensus(Kusama), Parachain(42), PalletInstance(42), GeneralIndex(42)),
);
let expected = MultiLocation::new(1, (GlobalConsensus(Kusama), Parachain(42)));
assert_eq!(relay_to_remote_para.chain_location(), expected);
// Parachain context pointing to relay chain,
let para_to_relay = MultiLocation::new(1, (PalletInstance(42), GeneralIndex(42)));
assert_eq!(para_to_relay.chain_location(), MultiLocation::parent());
// Parachain context pointing to sibling parachain,
let para_to_sibling =
MultiLocation::new(1, (Parachain(42), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(1, Parachain(42));
assert_eq!(para_to_sibling.chain_location(), expected);
// Parachain context pointing to different consensus relay,
let para_to_remote_relay =
MultiLocation::new(2, (GlobalConsensus(Kusama), PalletInstance(42), GeneralIndex(42)));
let expected = MultiLocation::new(2, GlobalConsensus(Kusama));
assert_eq!(para_to_remote_relay.chain_location(), expected);
// Parachain context pointing to different consensus parachain,
let para_to_remote_para = MultiLocation::new(
2,
(GlobalConsensus(Kusama), Parachain(42), PalletInstance(42), GeneralIndex(42)),
);
let expected = MultiLocation::new(2, (GlobalConsensus(Kusama), Parachain(42)));
assert_eq!(para_to_remote_para.chain_location(), expected);
}
#[test]
fn conversion_from_other_types_works() {
use crate::v2;