Skip to content

Commit c0b2711

Browse files
authored
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 <>
1 parent 7e20f20 commit c0b2711

File tree

5 files changed

+1631
-386
lines changed

5 files changed

+1631
-386
lines changed

pallet-xcm/src/benchmarking.rs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub trait Config: crate::Config {
4444
///
4545
/// Implementation should also make sure `dest` is reachable/connected.
4646
///
47-
/// If `None`, the benchmarks that depend on this will be skipped.
47+
/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
4848
fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> {
4949
None
5050
}
@@ -54,10 +54,27 @@ pub trait Config: crate::Config {
5454
///
5555
/// Implementation should also make sure `dest` is reachable/connected.
5656
///
57-
/// If `None`, the benchmarks that depend on this will be skipped.
57+
/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
5858
fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> {
5959
None
6060
}
61+
62+
/// Sets up a complex transfer (usually consisting of a teleport and reserve-based transfer), so
63+
/// that runtime can properly benchmark `transfer_assets()` extrinsic. Should return a tuple
64+
/// `(MultiAsset, u32, MultiLocation, dyn FnOnce())` representing the assets to transfer, the
65+
/// `u32` index of the asset to be used for fees, the destination chain for the transfer, and a
66+
/// `verify()` closure to verify the intended transfer side-effects.
67+
///
68+
/// Implementation should make sure the provided assets can be transacted by the runtime, there
69+
/// are enough balances in the involved accounts, and that `dest` is reachable/connected.
70+
///
71+
/// Used only in benchmarks.
72+
///
73+
/// If `None`, the benchmarks that depend on this will default to `Weight::MAX`.
74+
fn set_up_complex_asset_transfer(
75+
) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)> {
76+
None
77+
}
6178
}
6279

6380
benchmarks! {
@@ -158,6 +175,23 @@ benchmarks! {
158175
assert!(pallet_balances::Pallet::<T>::free_balance(&caller) <= balance - transferred_amount);
159176
}
160177

178+
transfer_assets {
179+
let (assets, fee_index, destination, verify) = T::set_up_complex_asset_transfer().ok_or(
180+
BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)),
181+
)?;
182+
let caller: T::AccountId = whitelisted_caller();
183+
let send_origin = RawOrigin::Signed(caller.clone());
184+
let recipient = [0u8; 32];
185+
let versioned_dest: VersionedMultiLocation = destination.into();
186+
let versioned_beneficiary: VersionedMultiLocation =
187+
AccountId32 { network: None, id: recipient.into() }.into();
188+
let versioned_assets: VersionedMultiAssets = assets.into();
189+
}: _<RuntimeOrigin<T>>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0, WeightLimit::Unlimited)
190+
verify {
191+
// run provided verification function
192+
verify();
193+
}
194+
161195
execute {
162196
let execute_origin =
163197
T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
@@ -302,3 +336,36 @@ benchmarks! {
302336
crate::mock::Test
303337
);
304338
}
339+
340+
pub mod helpers {
341+
use super::*;
342+
pub fn native_teleport_as_asset_transfer<T>(
343+
native_asset_location: MultiLocation,
344+
destination: MultiLocation,
345+
) -> Option<(MultiAssets, u32, MultiLocation, Box<dyn FnOnce()>)>
346+
where
347+
T: Config + pallet_balances::Config,
348+
u128: From<<T as pallet_balances::Config>::Balance>,
349+
{
350+
// Relay/native token can be teleported to/from AH.
351+
let amount = T::ExistentialDeposit::get() * 100u32.into();
352+
let assets: MultiAssets =
353+
MultiAsset { fun: Fungible(amount.into()), id: Concrete(native_asset_location) }.into();
354+
let fee_index = 0u32;
355+
356+
// Give some multiple of transferred amount
357+
let balance = amount * 10u32.into();
358+
let who = whitelisted_caller();
359+
let _ =
360+
<pallet_balances::Pallet::<T> as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance);
361+
// verify initial balance
362+
assert_eq!(pallet_balances::Pallet::<T>::free_balance(&who), balance);
363+
364+
// verify transferred successfully
365+
let verify = Box::new(move || {
366+
// verify balance after transfer, decreased by transferred amount (and delivery fees)
367+
assert!(pallet_balances::Pallet::<T>::free_balance(&who) <= balance - amount);
368+
});
369+
Some((assets, fee_index, destination, verify))
370+
}
371+
}

0 commit comments

Comments
 (0)