From ee88f2eb2a6385218f18924413748ee8741fb471 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Tue, 23 Apr 2024 18:13:31 +0300 Subject: [PATCH 1/4] pallet-xcm::transfer_assets_using_type() supports custom actions on destination Change `transfer_assets_using_type()` to not assume `DepositAssets` as the intended use of the assets on the destination. Instead provides the caller with the ability to specify custom XCM that be executed on `dest` chain as the last step of the transfer, thus allowing custom usecases for the transferred assets. E.g. some are used/swapped/etc there, while some are sent further to yet another chain. Note: this is an API change for `transfer_assets_using_type()`, but it is ok as the previous version has not been yet released. Thus, its first release will include the new API proposed by this PR. Signed-off-by: Adrian Catangiu --- .../src/tests/foreign_assets_transfers.rs | 30 ++++- .../src/tests/foreign_assets_transfers.rs | 30 ++++- .../src/tests/asset_transfers.rs | 6 +- .../src/tests/asset_transfers.rs | 6 +- polkadot/xcm/pallet-xcm/src/lib.rs | 110 +++++++++--------- 5 files changed, 112 insertions(+), 70 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs index 6bdf89e6f277e..8a705e72b576c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs @@ -54,14 +54,18 @@ fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) { fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::LocalReserve), bx!(fee.id.into()), bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -69,14 +73,18 @@ fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::DestinationReserve), bx!(fee.id.into()), bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -85,14 +93,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), bx!(fee.id.into()), bx!(TransferType::RemoteReserve(asset_hub_location.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -100,14 +112,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::Teleport), bx!(fee.id.into()), bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -115,14 +131,18 @@ fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> Dispatc fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::Teleport), bx!(fee.id.into()), bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs index 8cfda37c84c94..bba7f63f1dfec 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs @@ -54,14 +54,18 @@ fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) { fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::LocalReserve), bx!(fee.id.into()), bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -69,14 +73,18 @@ fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::DestinationReserve), bx!(fee.id.into()), bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -85,14 +93,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())), bx!(fee.id.into()), bx!(TransferType::RemoteReserve(asset_hub_location.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -100,14 +112,18 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::Teleport), bx!(fee.id.into()), bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } @@ -115,14 +131,18 @@ fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> Dispatc fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult { let fee_idx = t.args.fee_asset_item as usize; let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( t.signed_origin, bx!(t.args.dest.into()), - bx!(t.args.beneficiary.into()), bx!(t.args.assets.into()), bx!(TransferType::Teleport), bx!(fee.id.into()), bx!(TransferType::LocalReserve), + bx!(VersionedXcm::from(custom_xcm_on_dest)), t.args.weight_limit, ) } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 69d625be28045..566ea6da8e807 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -60,15 +60,19 @@ fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); let assets: Assets = (id.clone(), transfer_amount).into(); let fees_id: AssetId = id.into(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( signed_origin, bx!(destination.into()), - bx!(beneficiary.into()), bx!(assets.clone().into()), bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())), bx!(fees_id.into()), bx!(TransferType::RemoteReserve(local_asset_hub.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), WeightLimit::Unlimited, ) })); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index 3a8ce7d43f3e6..ffbd417a757f7 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -59,15 +59,19 @@ fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into(); let assets: Assets = (id.clone(), transfer_amount).into(); let fees_id: AssetId = id.into(); + let custom_xcm_on_dest = Xcm::<()>(vec![DepositAsset { + assets: Wild(AllCounted(assets.len() as u32)), + beneficiary, + }]); ::PolkadotXcm::transfer_assets_using_type( signed_origin, bx!(destination.into()), - bx!(beneficiary.into()), bx!(assets.into()), bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())), bx!(fees_id.into()), bx!(TransferType::RemoteReserve(local_asset_hub.into())), + bx!(VersionedXcm::from(custom_xcm_on_dest)), WeightLimit::Unlimited, ) })); diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 698ec6998b498..3f7790735994e 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -45,7 +45,7 @@ use sp_runtime::{ AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash, Saturating, Zero, }, - RuntimeDebug, + Either, RuntimeDebug, }; use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec}; use xcm::{latest::QueryResponseInfo, prelude::*}; @@ -1311,7 +1311,7 @@ pub mod pallet { Self::do_transfer_assets( origin, dest, - beneficiary, + Either::Left(beneficiary), assets, assets_transfer_type, fee_asset_item, @@ -1421,50 +1421,59 @@ pub mod pallet { /// - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to /// mint/teleport assets and deposit them to `beneficiary`. /// - /// Fee payment on the source, destination and all intermediary hops, is specified through - /// `fees_id`, but make sure enough of the specified `fees_id` asset is included in the - /// given list of `assets`. `fees_id` should be enough to pay for `weight_limit`. If more - /// weight is needed than `weight_limit`, then the operation will fail and the sent assets - /// may be at risk. + /// On the destination chain, as well as any intermediary hops, `BuyExecution` is used to + /// buy execution using transferred `assets` identified by `fees_id`. + /// Make sure enough of the specified `fees_id` asset is included in the given list of + /// `assets`. `fees_id` should be enough to pay for `weight_limit`. If more weight is needed + /// than `weight_limit`, then the operation will fail and the sent assets may be at risk. /// /// `fees_id` may use different transfer type than rest of `assets` and can be specified /// through `fees_transfer_type`. /// + /// The caller needs to specify what should happen to the transferred assets once they reach + /// the `dest` chain. This is done through the `custom_xcm_on_dest` parameter, which + /// contains the instructions to execute on `dest` as a final step. + /// This is usually as simple as: + /// `Xcm::<()>(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary + /// }])`, but could be something more exotic like sending the `assets` even further. + /// /// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. /// - `dest`: Destination context for the assets. Will typically be `[Parent, /// Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from /// relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from /// parachain across a bridge to another ecosystem destination. - /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will - /// generally be an `AccountId32` value. /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the /// fee on the `dest` (and possibly reserve) chains. /// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`. /// - `fees_id`: One of the included `assets` to be be used to pay fees. /// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets. + /// - `custom_xcm_on_dest`: The XCM to be executed on `dest` chain as the last step of the + /// transfer, which also determines what happens to the assets on the destination chain. /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::transfer_assets())] pub fn transfer_assets_using_type( origin: OriginFor, dest: Box, - beneficiary: Box, assets: Box, assets_transfer_type: Box, - fees_id: Box, + remote_fees_id: Box, fees_transfer_type: Box, + custom_xcm_on_dest: Box>, weight_limit: WeightLimit, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; let dest: Location = (*dest).try_into().map_err(|()| Error::::BadVersion)?; - let beneficiary: Location = - (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; let assets: Assets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - let fees_id: AssetId = (*fees_id).try_into().map_err(|()| Error::::BadVersion)?; + let fees_id: AssetId = + (*remote_fees_id).try_into().map_err(|()| Error::::BadVersion)?; + let remote_xcm: Xcm<()> = + (*custom_xcm_on_dest).try_into().map_err(|()| Error::::BadVersion)?; log::debug!( target: "xcm::pallet_xcm::transfer_assets_using_type", - "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?} through {:?}, fees-id {:?} through {:?}", - origin_location, dest, beneficiary, assets, assets_transfer_type, fees_id, fees_transfer_type, + "origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \ + remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \ + custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}", ); let assets = assets.into_inner(); @@ -1475,7 +1484,7 @@ pub mod pallet { Self::do_transfer_assets( origin_location, dest, - beneficiary, + Either::Right(remote_xcm), assets, *assets_transfer_type, fee_asset_index, @@ -1650,7 +1659,7 @@ impl Pallet { let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( origin.clone(), dest.clone(), - beneficiary, + Either::Left(beneficiary), assets, assets_transfer_type, FeesHandling::Batched { fees }, @@ -1692,7 +1701,7 @@ impl Pallet { let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type( origin_location.clone(), dest.clone(), - beneficiary, + Either::Left(beneficiary), assets, TransferType::Teleport, FeesHandling::Batched { fees }, @@ -1704,7 +1713,7 @@ impl Pallet { fn do_transfer_assets( origin: Location, dest: Location, - beneficiary: Location, + beneficiary: Either>, mut assets: Vec, assets_transfer_type: TransferType, fee_asset_index: usize, @@ -1770,7 +1779,7 @@ impl Pallet { fn build_xcm_transfer_type( origin: Location, dest: Location, - beneficiary: Location, + beneficiary: Either>, assets: Vec, transfer_type: TransferType, fees: FeesHandling, @@ -1782,27 +1791,38 @@ impl Pallet { fees_handling {:?}, weight_limit: {:?}", origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, ); + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_remote_xcm = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // max assets is `assets` (+ potentially separately handled fee) + let max_assets = assets.len() as u32 + + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; Ok(match transfer_type { TransferType::LocalReserve => { - let (local, remote) = Self::local_reserve_transfer_programs( + let (local, mut remote) = Self::local_reserve_transfer_programs( origin.clone(), dest.clone(), - beneficiary, assets, fees, weight_limit, )?; + remote.0.extend(custom_remote_xcm.into_iter()); (local, Some(remote)) }, TransferType::DestinationReserve => { - let (local, remote) = Self::destination_reserve_transfer_programs( + let (local, mut remote) = Self::destination_reserve_transfer_programs( origin.clone(), dest.clone(), - beneficiary, assets, fees, weight_limit, )?; + remote.0.extend(custom_remote_xcm.into_iter()); (local, Some(remote)) }, TransferType::RemoteReserve(reserve) => { @@ -1814,22 +1834,22 @@ impl Pallet { origin.clone(), reserve.try_into().map_err(|()| Error::::BadVersion)?, dest.clone(), - beneficiary, assets, fees, weight_limit, + custom_remote_xcm, )?; (local, None) }, TransferType::Teleport => { - let (local, remote) = Self::teleport_assets_program( + let (local, mut remote) = Self::teleport_assets_program( origin.clone(), dest.clone(), - beneficiary, assets, fees, weight_limit, )?; + remote.0.extend(custom_remote_xcm.into_iter()); (local, Some(remote)) }, }) @@ -1947,7 +1967,6 @@ impl Pallet { fn local_reserve_transfer_programs( origin: Location, dest: Location, - beneficiary: Location, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -1956,9 +1975,6 @@ impl Pallet { ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); let (_, assets) = value; - // max assets is `assets` (+ potentially separately handled fee) - let max_assets = - assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: Assets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -1980,10 +1996,6 @@ impl Pallet { ]); // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; - // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest - .inner_mut() - .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); Ok((local_execute_xcm, xcm_on_dest)) } @@ -2022,7 +2034,6 @@ impl Pallet { fn destination_reserve_transfer_programs( origin: Location, dest: Location, - beneficiary: Location, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -2031,9 +2042,6 @@ impl Pallet { ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); let (_, assets) = value; - // max assets is `assets` (+ potentially separately handled fee) - let max_assets = - assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: Assets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -2058,11 +2066,6 @@ impl Pallet { // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; - // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest - .inner_mut() - .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); - Ok((local_execute_xcm, xcm_on_dest)) } @@ -2071,10 +2074,10 @@ impl Pallet { origin: Location, reserve: Location, dest: Location, - beneficiary: Location, assets: Vec, fees: Asset, weight_limit: WeightLimit, + custom_xcm_on_dest: Xcm<()>, ) -> Result::RuntimeCall>, Error> { let value = (origin, assets); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); @@ -2096,10 +2099,9 @@ impl Pallet { // identifies `dest` as seen by `reserve` let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::::CannotReanchor)?; // xcm to be executed at dest - let xcm_on_dest = Xcm(vec![ - BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }, - DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, - ]); + let mut xcm_on_dest = + Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]); + xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter()); // xcm to be executed on reserve let xcm_on_reserve = Xcm(vec![ BuyExecution { fees: reserve_fees, weight_limit }, @@ -2171,7 +2173,6 @@ impl Pallet { fn teleport_assets_program( origin: Location, dest: Location, - beneficiary: Location, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -2180,9 +2181,6 @@ impl Pallet { ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); let (_, assets) = value; - // max assets is `assets` (+ potentially separately handled fee) - let max_assets = - assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let context = T::UniversalLocation::get(); let assets: Assets = assets.into(); let mut reanchored_assets = assets.clone(); @@ -2231,10 +2229,6 @@ impl Pallet { ]); // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; - // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest - .inner_mut() - .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); Ok((local_execute_xcm, xcm_on_dest)) } From 072df8e3cb6b074fe2edab347994f05ed36a58cd Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 24 Apr 2024 10:52:53 +0300 Subject: [PATCH 2/4] add test DOT from relay to para through asset hub --- .../tests/assets/asset-hub-rococo/src/lib.rs | 5 +- ...ssets_transfers.rs => hybrid_transfers.rs} | 163 ++++++++++++++++++ .../assets/asset-hub-rococo/src/tests/mod.rs | 2 +- .../src/tests/reserve_transfer.rs | 2 +- .../tests/assets/asset-hub-westend/src/lib.rs | 5 +- ...ssets_transfers.rs => hybrid_transfers.rs} | 163 ++++++++++++++++++ .../assets/asset-hub-westend/src/tests/mod.rs | 2 +- .../src/tests/reserve_transfer.rs | 2 +- 8 files changed, 338 insertions(+), 6 deletions(-) rename cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/{foreign_assets_transfers.rs => hybrid_transfers.rs} (79%) rename cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/{foreign_assets_transfers.rs => hybrid_transfers.rs} (79%) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index 322c6cf1f2282..2bd388bee400e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -70,7 +70,9 @@ mod imports { LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, }; - pub use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig; + pub use rococo_runtime::xcm_config::{ + UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig, + }; pub const ASSET_ID: u32 = 3; pub const ASSET_MIN_BALANCE: u128 = 1000; @@ -83,6 +85,7 @@ mod imports { pub type ParaToSystemParaTest = Test; pub type ParaToParaThroughRelayTest = Test; pub type ParaToParaThroughAHTest = Test; + pub type RelayToParaThroughAHTest = Test; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs similarity index 79% rename from cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index 8a705e72b576c..f95962522c2ee 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/foreign_assets_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -646,3 +646,166 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici asset_hub_to_para_teleport_foreign_assets, ); } + +// =============================================================== +// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ==== +// =============================================================== +/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid +/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their +/// Sovereign Account on Asset Hub. +#[test] +fn transfer_native_asset_from_relay_to_para_through_asset_hub() { + // Init values for Relay + let destination = Rococo::child_location_of(PenpalA::para_id()); + let sender = RococoSender::get(); + let amount_to_send: Balance = ROCOCO_ED * 1000; + + // Init values for Parachain + let relay_native_asset_location = RelayLocation::get(); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send), + }; + let mut test = RelayToParaThroughAHTest::new(test_args); + + let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + // Query initial balances + let sender_balance_before = test.sender.balance; + let sov_penpal_on_ah_before = AssetHubRococo::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &receiver) + }); + + fn relay_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + Rococo::assert_xcm_pallet_attempted_complete(None); + assert_expected_events!( + Rococo, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); + } + fn asset_hub_assertions(_: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ah = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + assert_expected_events!( + AssetHubRococo, + vec![ + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + } + fn penpal_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let expected_id = + t.args.assets.into_inner().first().unwrap().id.0.clone().try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } + fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location = Rococo::child_location_of(AssetHubRococo::para_id()); + let context = RococoUniversalLocation::get(); + + // reanchor fees to the view of destination (Penpal) + let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap(); + if let Fungible(ref mut amount) = remote_fees.fun { + // we already spent some fees along the way, just use half of what we started with + *amount = *amount / 2; + } + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() }, + DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }, + ]); + + // reanchor final dest (Penpal) to the view of hop (Asset Hub) + let mut dest = t.args.dest.clone(); + dest.reanchor(&asset_hub_location, &context).unwrap(); + // on Asset Hub, forward assets to Penpal + let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + dest, + xcm: xcm_on_final_dest, + }]); + + // First leg is a teleport, from there a local-reserve-transfer to final dest + ::XcmPallet::transfer_assets_using_type( + t.signed_origin, + bx!(asset_hub_location.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(xcm_on_hop)), + t.args.weight_limit, + ) + } + + // Set assertions and dispatchables + test.set_assertion::(relay_assertions); + test.set_assertion::(asset_hub_assertions); + test.set_assertion::(penpal_assertions); + test.set_dispatchable::(transfer_assets_dispatchable); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sov_penpal_on_ah_after = AssetHubRococo::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - amount_to_send); + // SA on AH balance is increased + assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before); + // Receiver's asset balance is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + amount_to_send); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index 346af30823848..138ce419757b9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod foreign_assets_transfers; +mod hybrid_transfers; mod reserve_transfer; mod send; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index 5aef70f5cbfc0..8b9fedcd4947c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -574,7 +574,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender = RococoSender::get(); let amount_to_send: Balance = ROCOCO_ED * 1000; - // Init values fot Parachain + // Init values for Parachain let relay_native_asset_location = RelayLocation::get(); let receiver = PenpalAReceiver::get(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index e687251c14f9e..1c4a0ef4c8d2a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -74,7 +74,9 @@ mod imports { LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, }; - pub use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig; + pub use westend_runtime::xcm_config::{ + UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + }; pub const ASSET_ID: u32 = 3; pub const ASSET_MIN_BALANCE: u128 = 1000; @@ -87,6 +89,7 @@ mod imports { pub type ParaToSystemParaTest = Test; pub type ParaToParaThroughRelayTest = Test; pub type ParaToParaThroughAHTest = Test; + pub type RelayToParaThroughAHTest = Test; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs similarity index 79% rename from cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs rename to cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index bba7f63f1dfec..dd2f62757b08c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/foreign_assets_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -647,3 +647,166 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici asset_hub_to_para_teleport_foreign_assets, ); } + +// =============================================================== +// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ==== +// =============================================================== +/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid +/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their +/// Sovereign Account on Asset Hub. +#[test] +fn transfer_native_asset_from_relay_to_para_through_asset_hub() { + // Init values for Relay + let destination = Westend::child_location_of(PenpalA::para_id()); + let sender = WestendSender::get(); + let amount_to_send: Balance = WESTEND_ED * 1000; + + // Init values for Parachain + let relay_native_asset_location = RelayLocation::get(); + let receiver = PenpalAReceiver::get(); + + // Init Test + let test_args = TestContext { + sender, + receiver: receiver.clone(), + args: TestArgs::new_relay(destination.clone(), receiver.clone(), amount_to_send), + }; + let mut test = RelayToParaThroughAHTest::new(test_args); + + let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + // Query initial balances + let sender_balance_before = test.sender.balance; + let sov_penpal_on_ah_before = AssetHubWestend::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_assets_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &receiver) + }); + + fn relay_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + Westend::assert_xcm_pallet_attempted_complete(None); + assert_expected_events!( + Westend, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + // Amount to teleport is deposited in Relay's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); + } + fn asset_hub_assertions(_: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + assert_expected_events!( + AssetHubWestend, + vec![ + // Deposited to receiver parachain SA + RuntimeEvent::Balances( + pallet_balances::Event::Minted { who, .. } + ) => { + who: *who == sov_penpal_on_ah, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + } + fn penpal_assertions(t: RelayToParaThroughAHTest) { + type RuntimeEvent = ::RuntimeEvent; + let expected_id = + t.args.assets.into_inner().first().unwrap().id.0.clone().try_into().unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => { + asset_id: *asset_id == expected_id, + owner: *owner == t.receiver.account_id, + }, + ] + ); + } + fn transfer_assets_dispatchable(t: RelayToParaThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location = Westend::child_location_of(AssetHubWestend::para_id()); + let context = WestendUniversalLocation::get(); + + // reanchor fees to the view of destination (Penpal) + let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap(); + if let Fungible(ref mut amount) = remote_fees.fun { + // we already spent some fees along the way, just use half of what we started with + *amount = *amount / 2; + } + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() }, + DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }, + ]); + + // reanchor final dest (Penpal) to the view of hop (Asset Hub) + let mut dest = t.args.dest.clone(); + dest.reanchor(&asset_hub_location, &context).unwrap(); + // on Asset Hub, forward assets to Penpal + let xcm_on_hop = Xcm::<()>(vec![DepositReserveAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + dest, + xcm: xcm_on_final_dest, + }]); + + // First leg is a teleport, from there a local-reserve-transfer to final dest + ::XcmPallet::transfer_assets_using_type( + t.signed_origin, + bx!(asset_hub_location.into()), + bx!(t.args.assets.into()), + bx!(TransferType::Teleport), + bx!(fee.id.into()), + bx!(TransferType::Teleport), + bx!(VersionedXcm::from(xcm_on_hop)), + t.args.weight_limit, + ) + } + + // Set assertions and dispatchables + test.set_assertion::(relay_assertions); + test.set_assertion::(asset_hub_assertions); + test.set_assertion::(penpal_assertions); + test.set_dispatchable::(transfer_assets_dispatchable); + test.assert(); + + // Query final balances + let sender_balance_after = test.sender.balance; + let sov_penpal_on_ah_after = AssetHubWestend::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah) + }); + let receiver_assets_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location, &receiver) + }); + + // Sender's balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - amount_to_send); + // SA on AH balance is increased + assert!(sov_penpal_on_ah_after > sov_penpal_on_ah_before); + // Receiver's asset balance is increased + assert!(receiver_assets_after > receiver_assets_before); + // Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_assets_after < receiver_assets_before + amount_to_send); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index e463e21e9e529..bf013697b4c75 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -14,7 +14,7 @@ // limitations under the License. mod fellowship_treasury; -mod foreign_assets_transfers; +mod hybrid_transfers; mod reserve_transfer; mod send; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index df01eb0d48ad9..65d013a0eec40 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -574,7 +574,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let sender = WestendSender::get(); let amount_to_send: Balance = WESTEND_ED * 1000; - // Init values fot Parachain + // Init values for Parachain let relay_native_asset_location = RelayLocation::get(); let receiver = PenpalAReceiver::get(); From 3f96ada86a71c38685b081920ba6015af20844e7 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 24 Apr 2024 11:06:10 +0300 Subject: [PATCH 3/4] rename the new extrinsic --- .../src/tests/hybrid_transfers.rs | 12 ++++----- .../src/tests/hybrid_transfers.rs | 12 ++++----- .../src/tests/asset_transfers.rs | 2 +- .../src/tests/asset_transfers.rs | 2 +- polkadot/xcm/pallet-xcm/src/lib.rs | 25 ++++++++++--------- prdoc/pr_3695.prdoc | 9 ++++++- 6 files changed, 35 insertions(+), 27 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index f95962522c2ee..edaaa998a9ca1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -58,7 +58,7 @@ fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -77,7 +77,7 @@ fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -97,7 +97,7 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -116,7 +116,7 @@ fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> Dispatc assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -135,7 +135,7 @@ fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> Dispatc assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -769,7 +769,7 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { }]); // First leg is a teleport, from there a local-reserve-transfer to final dest - ::XcmPallet::transfer_assets_using_type( + ::XcmPallet::transfer_assets_using_type_and_then( t.signed_origin, bx!(asset_hub_location.into()), bx!(t.args.assets.into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index dd2f62757b08c..d39c72c7c5f0d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -58,7 +58,7 @@ fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -77,7 +77,7 @@ fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -97,7 +97,7 @@ fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> Dispat assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -116,7 +116,7 @@ fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> Dispatc assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -135,7 +135,7 @@ fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> Dispatc assets: Wild(AllCounted(t.args.assets.len() as u32)), beneficiary: t.args.beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( t.signed_origin, bx!(t.args.dest.into()), bx!(t.args.assets.into()), @@ -770,7 +770,7 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { }]); // First leg is a teleport, from there a local-reserve-transfer to final dest - ::XcmPallet::transfer_assets_using_type( + ::XcmPallet::transfer_assets_using_type_and_then( t.signed_origin, bx!(asset_hub_location.into()), bx!(t.args.assets.into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 566ea6da8e807..87fb70e4de238 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -65,7 +65,7 @@ fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub( beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( signed_origin, bx!(destination.into()), bx!(assets.clone().into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index ffbd417a757f7..597e77d9049cf 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -64,7 +64,7 @@ fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub( beneficiary, }]); - ::PolkadotXcm::transfer_assets_using_type( + ::PolkadotXcm::transfer_assets_using_type_and_then( signed_origin, bx!(destination.into()), bx!(assets.into()), diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 3f7790735994e..e2709359e2e3d 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -1422,20 +1422,21 @@ pub mod pallet { /// mint/teleport assets and deposit them to `beneficiary`. /// /// On the destination chain, as well as any intermediary hops, `BuyExecution` is used to - /// buy execution using transferred `assets` identified by `fees_id`. - /// Make sure enough of the specified `fees_id` asset is included in the given list of - /// `assets`. `fees_id` should be enough to pay for `weight_limit`. If more weight is needed - /// than `weight_limit`, then the operation will fail and the sent assets may be at risk. + /// buy execution using transferred `assets` identified by `remote_fees_id`. + /// Make sure enough of the specified `remote_fees_id` asset is included in the given list + /// of `assets`. `remote_fees_id` should be enough to pay for `weight_limit`. If more weight + /// is needed than `weight_limit`, then the operation will fail and the sent assets may be + /// at risk. /// - /// `fees_id` may use different transfer type than rest of `assets` and can be specified - /// through `fees_transfer_type`. + /// `remote_fees_id` may use different transfer type than rest of `assets` and can be + /// specified through `fees_transfer_type`. /// /// The caller needs to specify what should happen to the transferred assets once they reach /// the `dest` chain. This is done through the `custom_xcm_on_dest` parameter, which /// contains the instructions to execute on `dest` as a final step. - /// This is usually as simple as: - /// `Xcm::<()>(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary - /// }])`, but could be something more exotic like sending the `assets` even further. + /// This is usually as simple as: + /// `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`, + /// but could be something more exotic like sending the `assets` even further. /// /// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. /// - `dest`: Destination context for the assets. Will typically be `[Parent, @@ -1445,14 +1446,14 @@ pub mod pallet { /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the /// fee on the `dest` (and possibly reserve) chains. /// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`. - /// - `fees_id`: One of the included `assets` to be be used to pay fees. + /// - `remote_fees_id`: One of the included `assets` to be be used to pay fees. /// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets. /// - `custom_xcm_on_dest`: The XCM to be executed on `dest` chain as the last step of the /// transfer, which also determines what happens to the assets on the destination chain. /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::transfer_assets())] - pub fn transfer_assets_using_type( + pub fn transfer_assets_using_type_and_then( origin: OriginFor, dest: Box, assets: Box, @@ -1470,7 +1471,7 @@ pub mod pallet { let remote_xcm: Xcm<()> = (*custom_xcm_on_dest).try_into().map_err(|()| Error::::BadVersion)?; log::debug!( - target: "xcm::pallet_xcm::transfer_assets_using_type", + target: "xcm::pallet_xcm::transfer_assets_using_type_and_then", "origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \ remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \ custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}", diff --git a/prdoc/pr_3695.prdoc b/prdoc/pr_3695.prdoc index 2c2c2b2e69178..cc54fb240cd02 100644 --- a/prdoc/pr_3695.prdoc +++ b/prdoc/pr_3695.prdoc @@ -6,7 +6,7 @@ title: "pallet-xcm: add new extrinsic for asset transfers using explicit reserve doc: - audience: Runtime User description: | - pallet-xcm has a new extrinsic `transfer_assets_using_type` for transferring + pallet-xcm has a new extrinsic `transfer_assets_using_type_and_then` for transferring assets from local chain to destination chain using an explicit XCM transfer types for transferring the assets and the fees: - `TransferType::LocalReserve`: transfer assets to sovereign account of destination @@ -33,6 +33,13 @@ doc: Same when transferring bridged assets back across the bridge, the local bridging parachain must be used as the explicit reserve location. + The new method takes a `custom_xcm_on_dest` parameter allowing the caller to specify + what should happen to the transferred assets once they reach + the `dest` chain. The `custom_xcm_on_dest` parameter should contains the instructions + to execute on `dest` as a final step. Usually as simple as: + `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`, + but could be something more exotic like sending the `assets` even further. + crates: - name: pallet-xcm bump: minor From c6d95fef20613f53b61dd71214c0dadcb91af0ab Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 24 Apr 2024 11:21:33 +0300 Subject: [PATCH 4/4] address comments --- polkadot/xcm/pallet-xcm/src/lib.rs | 135 ++++++++++++++++++----------- 1 file changed, 84 insertions(+), 51 deletions(-) diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index e2709359e2e3d..f6c301d5b04e2 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -1792,68 +1792,51 @@ impl Pallet { fees_handling {:?}, weight_limit: {:?}", origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, ); - // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. - let custom_remote_xcm = match beneficiary { - Either::Right(custom_xcm) => custom_xcm, - Either::Left(beneficiary) => { - // max assets is `assets` (+ potentially separately handled fee) - let max_assets = assets.len() as u32 + - if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; - // deposit all remaining assets in holding to `beneficiary` location - Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) - }, - }; - Ok(match transfer_type { - TransferType::LocalReserve => { - let (local, mut remote) = Self::local_reserve_transfer_programs( - origin.clone(), - dest.clone(), - assets, - fees, - weight_limit, - )?; - remote.0.extend(custom_remote_xcm.into_iter()); - (local, Some(remote)) - }, - TransferType::DestinationReserve => { - let (local, mut remote) = Self::destination_reserve_transfer_programs( - origin.clone(), - dest.clone(), - assets, - fees, - weight_limit, - )?; - remote.0.extend(custom_remote_xcm.into_iter()); - (local, Some(remote)) - }, + match transfer_type { + TransferType::LocalReserve => Self::local_reserve_transfer_programs( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + ) + .map(|(local, remote)| (local, Some(remote))), + TransferType::DestinationReserve => Self::destination_reserve_transfer_programs( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + ) + .map(|(local, remote)| (local, Some(remote))), TransferType::RemoteReserve(reserve) => { let fees = match fees { FeesHandling::Batched { fees } => fees, _ => return Err(Error::::InvalidAssetUnsupportedReserve.into()), }; - let local = Self::remote_reserve_transfer_program( + Self::remote_reserve_transfer_program( origin.clone(), reserve.try_into().map_err(|()| Error::::BadVersion)?, + beneficiary, dest.clone(), assets, fees, weight_limit, - custom_remote_xcm, - )?; - (local, None) - }, - TransferType::Teleport => { - let (local, mut remote) = Self::teleport_assets_program( - origin.clone(), - dest.clone(), - assets, - fees, - weight_limit, - )?; - remote.0.extend(custom_remote_xcm.into_iter()); - (local, Some(remote)) + ) + .map(|local| (local, None)) }, - }) + TransferType::Teleport => Self::teleport_assets_program( + origin.clone(), + dest.clone(), + beneficiary, + assets, + fees, + weight_limit, + ) + .map(|(local, remote)| (local, Some(remote))), + } } fn execute_xcm_transfer( @@ -1968,6 +1951,7 @@ impl Pallet { fn local_reserve_transfer_programs( origin: Location, dest: Location, + beneficiary: Either>, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -1976,6 +1960,9 @@ impl Pallet { ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); let (_, assets) = value; + // max assets is `assets` (+ potentially separately handled fee) + let max_assets = + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: Assets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -1998,6 +1985,16 @@ impl Pallet { // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_remote_xcm = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; + xcm_on_dest.0.extend(custom_remote_xcm.into_iter()); + Ok((local_execute_xcm, xcm_on_dest)) } @@ -2035,6 +2032,7 @@ impl Pallet { fn destination_reserve_transfer_programs( origin: Location, dest: Location, + beneficiary: Either>, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -2043,6 +2041,9 @@ impl Pallet { ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); let (_, assets) = value; + // max assets is `assets` (+ potentially separately handled fee) + let max_assets = + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: Assets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -2067,6 +2068,16 @@ impl Pallet { // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_remote_xcm = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; + xcm_on_dest.0.extend(custom_remote_xcm.into_iter()); + Ok((local_execute_xcm, xcm_on_dest)) } @@ -2074,11 +2085,11 @@ impl Pallet { fn remote_reserve_transfer_program( origin: Location, reserve: Location, + beneficiary: Either>, dest: Location, assets: Vec, fees: Asset, weight_limit: WeightLimit, - custom_xcm_on_dest: Xcm<()>, ) -> Result::RuntimeCall>, Error> { let value = (origin, assets); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); @@ -2102,6 +2113,14 @@ impl Pallet { // xcm to be executed at dest let mut xcm_on_dest = Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]); + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_xcm_on_dest = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter()); // xcm to be executed on reserve let xcm_on_reserve = Xcm(vec![ @@ -2174,6 +2193,7 @@ impl Pallet { fn teleport_assets_program( origin: Location, dest: Location, + beneficiary: Either>, assets: Vec, fees: FeesHandling, weight_limit: WeightLimit, @@ -2182,6 +2202,9 @@ impl Pallet { ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); let (_, assets) = value; + // max assets is `assets` (+ potentially separately handled fee) + let max_assets = + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let context = T::UniversalLocation::get(); let assets: Assets = assets.into(); let mut reanchored_assets = assets.clone(); @@ -2231,6 +2254,16 @@ impl Pallet { // handle fees Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; + // Use custom XCM on remote chain, or just default to depositing everything to beneficiary. + let custom_remote_xcm = match beneficiary { + Either::Right(custom_xcm) => custom_xcm, + Either::Left(beneficiary) => { + // deposit all remaining assets in holding to `beneficiary` location + Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }]) + }, + }; + xcm_on_dest.0.extend(custom_remote_xcm.into_iter()); + Ok((local_execute_xcm, xcm_on_dest)) }