pallet-xcm: add new flexible transfer_assets() call/extrinsic (#2388)

# Motivation (+testing)

### Enable easy `ForeignAssets` transfers using `pallet-xcm` 

We had just previously added capabilities to teleport fees during
reserve-based transfers, but what about reserve-transferring fees when
needing to teleport some non-fee asset?

This PR aligns everything under either explicit reserve-transfer,
explicit teleport, or this new flexible `transfer_assets()` which can
mix and match as needed with fewer artificial constraints imposed to the
user.

This will enable, for example, a (non-system) parachain to teleport
their `ForeignAssets` assets to AssetHub while using DOT to pay fees.
(the assets are teleported - as foreign assets should from their owner
chain - while DOT used for fees can only be reserve-based transferred
between said parachain and AssetHub).

Added `xcm-emulator` tests for this scenario ^.

# Description

Reverts `(limited_)reserve_transfer_assets` to only allow reserve-based
transfers for all `assets` including fees.

Similarly `(limited_)teleport_assets` only allows teleports for all
`assets` including fees.
    
For complex combinations of asset transfers where assets and fees may
have different reserves or different reserve/teleport trust
configurations, users can use the newly added `transfer_assets()`
extrinsic which is more flexible in allowing more complex scenarios.

`assets` (excluding `fees`) must have same reserve location or otherwise
be teleportable to `dest`.
No limitations imposed on `fees`.

- for local reserve: transfer assets to sovereign account of destination
chain and forward a notification XCM to `dest` to mint and deposit
reserve-based assets to `beneficiary`.
- for destination reserve: burn local assets and forward a notification
to `dest` chain to withdraw the reserve assets from this chain's
sovereign account and deposit them to `beneficiary`.
- for remote reserve: burn local assets, forward XCM to reserve chain to
move reserves from this chain's SA to `dest` chain's SA, and forward
another XCM to `dest` to mint and deposit reserve-based assets to
`beneficiary`.
- for teleports: burn local assets and forward XCM to `dest` chain to
mint/teleport assets and deposit them to `beneficiary`.

## Review notes

Only around 500 lines are prod code (see `pallet_xcm/src/lib.rs`), the
rest of the PR is new tests and improving existing tests.

---------

Co-authored-by: command-bot <>
This commit is contained in:
Adrian Catangiu
2023-12-06 13:18:12 +02:00
committed by GitHub
parent 066bad6329
commit e7651cf41b
38 changed files with 3320 additions and 955 deletions
@@ -1431,6 +1431,55 @@ impl_runtime_apis! {
ParentThen(Parachain(random_para_id).into()).into(),
))
}
fn set_up_complex_asset_transfer(
) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> {
// Transfer to Relay some local AH asset (local-reserve-transfer) while paying
// fees using teleported native token.
// (We don't care that Relay doesn't accept incoming unknown AH local asset)
let dest = Parent.into();
let fee_amount = EXISTENTIAL_DEPOSIT;
let fee_asset: MultiAsset = (MultiLocation::parent(), fee_amount).into();
let who = frame_benchmarking::whitelisted_caller();
// Give some multiple of the existential deposit
let balance = fee_amount + EXISTENTIAL_DEPOSIT * 1000;
let _ = <Balances as frame_support::traits::Currency<_>>::make_free_balance_be(
&who, balance,
);
// verify initial balance
assert_eq!(Balances::free_balance(&who), balance);
// set up local asset
let asset_amount = 10u128;
let initial_asset_amount = asset_amount * 10;
let (asset_id, _, _) = pallet_assets::benchmarking::create_default_minted_asset::<
Runtime,
pallet_assets::Instance1
>(true, initial_asset_amount);
let asset_location = MultiLocation::new(
0,
X2(PalletInstance(50), GeneralIndex(u32::from(asset_id).into()))
);
let transfer_asset: MultiAsset = (asset_location, asset_amount).into();
let assets: MultiAssets = vec![fee_asset.clone(), transfer_asset].into();
let fee_index = if assets.get(0).unwrap().eq(&fee_asset) { 0 } else { 1 };
// verify transferred successfully
let verify = Box::new(move || {
// verify native balance after transfer, decreased by transferred fee amount
// (plus transport fees)
assert!(Balances::free_balance(&who) <= balance - fee_amount);
// verify asset balance decreased by exactly transferred amount
assert_eq!(
Assets::balance(asset_id.into(), &who),
initial_asset_amount - asset_amount,
);
});
Some((assets, fee_index as u32, dest, verify))
}
}
use pallet_xcm_bridge_hub_router::benchmarking::{