From 9a04ebbfb0b77f2dcdc46db6a1a2b455881edd03 Mon Sep 17 00:00:00 2001
From: girazoki <gorka.irazoki@gmail.com>
Date: Fri, 22 Mar 2024 19:48:15 +0100
Subject: [PATCH] [pallet-xcm] fix transport fees for remote reserve transfers
 (#3792)

Currently `transfer_assets` from pallet-xcm covers 4 main different
transfer types:
- `localReserve`
- `DestinationReserve`
- `Teleport`
- `RemoteReserve`

For the first three, the local execution and the remote message sending
are separated, and fees are deducted in pallet-xcm itself:
https://github.com/paritytech/polkadot-sdk/blob/3410dfb3929462da88be2da813f121d8b1cf46b3/polkadot/xcm/pallet-xcm/src/lib.rs#L1758.

For the 4th case `RemoteReserve`, pallet-xcm is still relying on the
xcm-executor itself to send the message (through the
`initiateReserveWithdraw` instruction). In this case, if delivery fees
need to be charged, it is not possible to do so because the
`jit_withdraw` mode has not being set.

This PR proposes to still use the `initiateReserveWithdraw` but
prepending a `setFeesMode { jit_withdraw: true }` to make sure delivery
fees can be paid.

A test-case is also added to present the aforementioned case

---------

Co-authored-by: Adrian Catangiu <adrian@parity.io>
---
 .../emulated/common/src/macros.rs             |   2 +-
 .../tests/assets/asset-hub-rococo/src/lib.rs  |   8 +-
 .../src/tests/reserve_transfer.rs             |  73 ++------
 .../asset-hub-rococo/src/tests/teleport.rs    |  14 +-
 .../tests/assets/asset-hub-westend/src/lib.rs |   8 +-
 .../src/tests/reserve_transfer.rs             |  71 ++------
 .../asset-hub-westend/src/tests/teleport.rs   |  14 +-
 .../people-rococo/src/tests/teleport.rs       |   6 +-
 .../people-westend/src/tests/teleport.rs      |   6 +-
 .../assets/test-utils/src/test_cases.rs       |   4 +-
 .../assets/test-utils/src/xcm_helpers.rs      |   5 +-
 .../runtimes/testing/penpal/src/lib.rs        |  19 +-
 .../runtimes/testing/penpal/src/xcm_config.rs |  19 +-
 polkadot/xcm/pallet-xcm/src/lib.rs            |   1 +
 polkadot/xcm/pallet-xcm/src/mock.rs           |  15 +-
 .../pallet-xcm/src/tests/assets_transfer.rs   | 171 ++++++++++++++++++
 prdoc/pr_3792.prdoc                           |  19 ++
 17 files changed, 291 insertions(+), 164 deletions(-)
 create mode 100644 prdoc/pr_3792.prdoc

diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs
index 6951de6faa7..6f6bbe41e01 100644
--- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs
+++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs
@@ -115,7 +115,7 @@ macro_rules! test_parachain_is_trusted_teleporter {
 					let para_receiver_balance_after =
 						<$receiver_para as $crate::macros::Chain>::account_data_of(receiver.clone()).free;
 					let delivery_fees = <$sender_para>::execute_with(|| {
-						$crate::macros::asset_test_utils::xcm_helpers::transfer_assets_delivery_fees::<
+						$crate::macros::asset_test_utils::xcm_helpers::teleport_assets_delivery_fees::<
 							<$sender_xcm_config as xcm_executor::Config>::XcmSender,
 						>($assets.clone(), fee_asset_item, weight_limit.clone(), beneficiary, para_destination)
 					});
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 21d858f1fe5..a5a4914e21d 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
@@ -63,17 +63,13 @@ mod imports {
 
 	// Runtimes
 	pub use asset_hub_rococo_runtime::xcm_config::{
-		TokenLocation as RelayLocation, UniversalLocation as AssetHubRococoUniversalLocation,
-		XcmConfig as AssetHubRococoXcmConfig,
+		TokenLocation as RelayLocation, XcmConfig as AssetHubRococoXcmConfig,
 	};
 	pub use penpal_runtime::xcm_config::{
 		LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
 		LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
-		UniversalLocation as PenpalUniversalLocation, XcmConfig as PenpalRococoXcmConfig,
-	};
-	pub use rococo_runtime::xcm_config::{
-		UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig,
 	};
+	pub use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig;
 
 	pub const ASSET_ID: u32 = 3;
 	pub const ASSET_MIN_BALANCE: u128 = 1000;
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 705c9613b64..0a5956dedfd 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
@@ -524,7 +524,6 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let destination = Rococo::child_location_of(PenpalA::para_id());
 	let sender = RococoSender::get();
 	let amount_to_send: Balance = ROCOCO_ED * 1000;
-	let assets: Assets = (Here, amount_to_send).into();
 
 	// Init values fot Parachain
 	let relay_native_asset_location =
@@ -552,15 +551,6 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	test.set_dispatchable::<Rococo>(relay_to_para_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = Rococo::execute_with(|| {
-		let reanchored_assets =
-			assets.reanchored(&destination, &RococoUniversalLocation::get()).unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<RococoXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_balance_after = test.sender.balance;
 	let receiver_assets_after = PenpalA::execute_with(|| {
@@ -568,8 +558,8 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &receiver)
 	});
 
-	// Sender's balance is reduced
-	assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_balance_after < sender_balance_before - amount_to_send);
 	// 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`;
@@ -595,7 +585,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
 		relay_native_asset_location,
 		sender.clone(),
-		amount_to_send,
+		amount_to_send * 2,
 	);
 
 	// Init values for Relay
@@ -634,15 +624,6 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	test.set_dispatchable::<PenpalA>(para_to_relay_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = PenpalA::execute_with(|| {
-		let reanchored_assets =
-			assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<PenpalRococoXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -650,8 +631,8 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	});
 	let receiver_balance_after = test.receiver.balance;
 
-	// Sender's balance is reduced
-	assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_assets_after < sender_assets_before - amount_to_send);
 	// Receiver's asset balance is increased
 	assert!(receiver_balance_after > receiver_balance_before);
 	// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
@@ -705,16 +686,6 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 	test.set_dispatchable::<AssetHubRococo>(system_para_to_para_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = AssetHubRococo::execute_with(|| {
-		let reanchored_assets = assets
-			.reanchored(&destination, &AssetHubRococoUniversalLocation::get())
-			.unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<AssetHubRococoXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_balance_after = test.sender.balance;
 	let receiver_assets_after = PenpalA::execute_with(|| {
@@ -722,8 +693,8 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &receiver)
 	});
 
-	// Sender's balance is reduced
-	assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_balance_after < sender_balance_before - amount_to_send);
 	// Receiver's assets is increased
 	assert!(receiver_assets_after > receiver_assets_before);
 	// Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`;
@@ -738,7 +709,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	// Init values for Parachain
 	let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id());
 	let sender = PenpalASender::get();
-	let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000;
+	let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
 	let assets: Assets = (Parent, amount_to_send).into();
 	let system_para_native_asset_location =
 		v3::Location::try_from(RelayLocation::get()).expect("conversion works");
@@ -749,7 +720,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
 		system_para_native_asset_location,
 		sender.clone(),
-		amount_to_send,
+		amount_to_send * 2,
 	);
 
 	// Init values for System Parachain
@@ -788,15 +759,6 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	test.set_dispatchable::<PenpalA>(para_to_system_para_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = PenpalA::execute_with(|| {
-		let reanchored_assets =
-			assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<PenpalRococoXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -804,8 +766,8 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	});
 	let receiver_balance_after = test.receiver.balance;
 
-	// Sender's balance is reduced
-	assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_assets_after < sender_assets_before - amount_to_send);
 	// Receiver's balance is increased
 	assert!(receiver_balance_after > receiver_balance_before);
 	// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
@@ -1084,7 +1046,7 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
 		relay_native_asset_location,
 		sender.clone(),
-		amount_to_send,
+		amount_to_send * 2,
 	);
 
 	// fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve
@@ -1118,13 +1080,6 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 	test.set_dispatchable::<PenpalA>(para_to_para_through_relay_limited_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = PenpalA::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<PenpalRococoXcmConfig as xcm_executor::Config>::XcmSender,
-		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -1135,8 +1090,8 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
 	});
 
-	// Sender's balance is reduced
-	assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_assets_after < sender_assets_before - amount_to_send);
 	// Receiver's balance is increased
 	assert!(receiver_assets_after > receiver_assets_before);
 }
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs
index 0cc5ddb9f64..4432999aa95 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs
@@ -322,7 +322,7 @@ fn limited_teleport_native_assets_from_relay_to_system_para_works() {
 	test.assert();
 
 	let delivery_fees = Rococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<RococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -369,7 +369,7 @@ fn limited_teleport_native_assets_back_from_system_para_to_relay_works() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = AssetHubRococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubRococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -410,7 +410,7 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = AssetHubRococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubRococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -445,7 +445,7 @@ fn teleport_native_assets_from_relay_to_system_para_works() {
 	test.assert();
 
 	let delivery_fees = Rococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<RococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -492,7 +492,7 @@ fn teleport_native_assets_back_from_system_para_to_relay_works() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = AssetHubRococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubRococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -530,7 +530,7 @@ fn teleport_native_assets_from_system_para_to_relay_fails() {
 	test.assert();
 
 	let delivery_fees = AssetHubRococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubRococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -593,7 +593,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
 		system_para_native_asset_location,
 		sender.clone(),
-		fee_amount_to_send,
+		fee_amount_to_send * 2,
 	);
 	// No need to create the asset (only mint) as it exists in genesis.
 	PenpalA::mint_asset(
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 3f899d1dbdb..c9f5fe0647e 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
@@ -67,17 +67,13 @@ mod imports {
 
 	// Runtimes
 	pub use asset_hub_westend_runtime::xcm_config::{
-		UniversalLocation as AssetHubWestendUniversalLocation, WestendLocation as RelayLocation,
-		XcmConfig as AssetHubWestendXcmConfig,
+		WestendLocation as RelayLocation, XcmConfig as AssetHubWestendXcmConfig,
 	};
 	pub use penpal_runtime::xcm_config::{
 		LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
 		LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
-		UniversalLocation as PenpalUniversalLocation, XcmConfig as PenpalWestendXcmConfig,
-	};
-	pub use westend_runtime::xcm_config::{
-		UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig,
 	};
+	pub use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig;
 
 	pub const ASSET_ID: u32 = 3;
 	pub const ASSET_MIN_BALANCE: u128 = 1000;
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 8c836132b54..64ad15ca312 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
@@ -524,7 +524,6 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	let destination = Westend::child_location_of(PenpalA::para_id());
 	let sender = WestendSender::get();
 	let amount_to_send: Balance = WESTEND_ED * 1000;
-	let assets: Assets = (Here, amount_to_send).into();
 
 	// Init values fot Parachain
 	let relay_native_asset_location =
@@ -552,15 +551,6 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 	test.set_dispatchable::<Westend>(relay_to_para_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = Westend::execute_with(|| {
-		let reanchored_assets =
-			assets.reanchored(&destination, &WestendUniversalLocation::get()).unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<WestendXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_balance_after = test.sender.balance;
 	let receiver_assets_after = PenpalA::execute_with(|| {
@@ -568,8 +558,8 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
 		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location.into(), &receiver)
 	});
 
-	// Sender's balance is reduced
-	assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_balance_after < sender_balance_before - amount_to_send);
 	// 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`;
@@ -595,7 +585,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
 		relay_native_asset_location,
 		sender.clone(),
-		amount_to_send,
+		amount_to_send * 2,
 	);
 
 	// Init values for Relay
@@ -634,15 +624,6 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	test.set_dispatchable::<PenpalA>(para_to_relay_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = PenpalA::execute_with(|| {
-		let reanchored_assets =
-			assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<PenpalWestendXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -650,8 +631,8 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
 	});
 	let receiver_balance_after = test.receiver.balance;
 
-	// Sender's balance is reduced
-	assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_assets_after < sender_assets_before - amount_to_send);
 	// Receiver's asset balance is increased
 	assert!(receiver_balance_after > receiver_balance_before);
 	// Receiver's asset balance increased by `amount_to_send - delivery_fees - bought_execution`;
@@ -705,16 +686,6 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 	test.set_dispatchable::<AssetHubWestend>(system_para_to_para_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = AssetHubWestend::execute_with(|| {
-		let reanchored_assets = assets
-			.reanchored(&destination, &AssetHubWestendUniversalLocation::get())
-			.unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<AssetHubWestendXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_balance_after = test.sender.balance;
 	let receiver_assets_after = PenpalA::execute_with(|| {
@@ -722,8 +693,8 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
 		<ForeignAssets as Inspect<_>>::balance(system_para_native_asset_location, &receiver)
 	});
 
-	// Sender's balance is reduced
-	assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_balance_after < sender_balance_before - amount_to_send);
 	// Receiver's assets is increased
 	assert!(receiver_assets_after > receiver_assets_before);
 	// Receiver's assets increased by `amount_to_send - delivery_fees - bought_execution`;
@@ -749,7 +720,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
 		system_para_native_asset_location,
 		sender.clone(),
-		amount_to_send,
+		amount_to_send * 2,
 	);
 
 	// Init values for System Parachain
@@ -789,15 +760,6 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	test.set_dispatchable::<PenpalA>(para_to_system_para_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = PenpalA::execute_with(|| {
-		let reanchored_assets =
-			assets.reanchored(&destination, &PenpalUniversalLocation::get()).unwrap();
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<PenpalWestendXcmConfig as xcm_executor::Config>::XcmSender,
-		>(reanchored_assets, 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -805,8 +767,8 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
 	});
 	let receiver_balance_after = test.receiver.balance;
 
-	// Sender's balance is reduced
-	assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_assets_after < sender_assets_before - amount_to_send);
 	// Receiver's balance is increased
 	assert!(receiver_balance_after > receiver_balance_before);
 	// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
@@ -1086,7 +1048,7 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner),
 		relay_native_asset_location,
 		sender.clone(),
-		amount_to_send,
+		amount_to_send * 2,
 	);
 
 	// fund the Parachain Origin's SA on Relay Chain with the native tokens held in reserve
@@ -1120,13 +1082,6 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 	test.set_dispatchable::<PenpalA>(para_to_para_through_relay_limited_reserve_transfer_assets);
 	test.assert();
 
-	// Calculate delivery fees
-	let delivery_fees = PenpalA::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
-			<PenpalWestendXcmConfig as xcm_executor::Config>::XcmSender,
-		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
-	});
-
 	// Query final balances
 	let sender_assets_after = PenpalA::execute_with(|| {
 		type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
@@ -1137,8 +1092,8 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
 		<ForeignAssets as Inspect<_>>::balance(relay_native_asset_location, &receiver)
 	});
 
-	// Sender's balance is reduced
-	assert_eq!(sender_assets_before - amount_to_send - delivery_fees, sender_assets_after);
+	// Sender's balance is reduced by amount sent plus delivery fees
+	assert!(sender_assets_after < sender_assets_before - amount_to_send);
 	// Receiver's balance is increased
 	assert!(receiver_assets_after > receiver_assets_before);
 }
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs
index 61f547fe7c5..aba05ea4322 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs
@@ -322,7 +322,7 @@ fn limited_teleport_native_assets_from_relay_to_system_para_works() {
 	test.assert();
 
 	let delivery_fees = Westend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<WestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -369,7 +369,7 @@ fn limited_teleport_native_assets_back_from_system_para_to_relay_works() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = AssetHubWestend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubWestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -410,7 +410,7 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = AssetHubWestend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubWestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -445,7 +445,7 @@ fn teleport_native_assets_from_relay_to_system_para_works() {
 	test.assert();
 
 	let delivery_fees = Westend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<WestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -492,7 +492,7 @@ fn teleport_native_assets_back_from_system_para_to_relay_works() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = AssetHubWestend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubWestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -530,7 +530,7 @@ fn teleport_native_assets_from_system_para_to_relay_fails() {
 	test.assert();
 
 	let delivery_fees = AssetHubWestend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<AssetHubWestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -593,7 +593,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
 		<PenpalA as Chain>::RuntimeOrigin::signed(asset_owner.clone()),
 		system_para_native_asset_location,
 		sender.clone(),
-		fee_amount_to_send,
+		fee_amount_to_send * 2,
 	);
 	// No need to create the asset (only mint) as it exists in genesis.
 	PenpalA::mint_asset(
diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs
index 3abe5c6cf66..350d87d638a 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs
@@ -155,7 +155,7 @@ fn limited_teleport_native_assets_from_relay_to_system_para_works() {
 	test.assert();
 
 	let delivery_fees = Rococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<RococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -204,7 +204,7 @@ fn limited_teleport_native_assets_back_from_system_para_to_relay_works() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = PeopleRococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<PeopleRococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -248,7 +248,7 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = PeopleRococo::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<PeopleRococoXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs
index eef35a99a83..8697477ba76 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs
@@ -155,7 +155,7 @@ fn limited_teleport_native_assets_from_relay_to_system_para_works() {
 	test.assert();
 
 	let delivery_fees = Westend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<WestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -204,7 +204,7 @@ fn limited_teleport_native_assets_back_from_system_para_to_relay_works() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = PeopleWestend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<PeopleWestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
@@ -248,7 +248,7 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() {
 	let receiver_balance_after = test.receiver.balance;
 
 	let delivery_fees = PeopleWestend::execute_with(|| {
-		xcm_helpers::transfer_assets_delivery_fees::<
+		xcm_helpers::teleport_assets_delivery_fees::<
 			<PeopleWestendXcmConfig as xcm_executor::Config>::XcmSender,
 		>(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest)
 	});
diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs
index 53e10956bd0..2f2624d8e52 100644
--- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs
+++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs
@@ -186,7 +186,7 @@ pub fn teleports_for_native_asset_works<
 
 				// Mint funds into account to ensure it has enough balance to pay delivery fees
 				let delivery_fees =
-					xcm_helpers::transfer_assets_delivery_fees::<XcmConfig::XcmSender>(
+					xcm_helpers::teleport_assets_delivery_fees::<XcmConfig::XcmSender>(
 						(native_asset_id.clone(), native_asset_to_teleport_away.into()).into(),
 						0,
 						Unlimited,
@@ -579,7 +579,7 @@ pub fn teleports_for_foreign_assets_works<
 
 				// Make sure the target account has enough native asset to pay for delivery fees
 				let delivery_fees =
-					xcm_helpers::transfer_assets_delivery_fees::<XcmConfig::XcmSender>(
+					xcm_helpers::teleport_assets_delivery_fees::<XcmConfig::XcmSender>(
 						(foreign_asset_id_location_latest.clone(), asset_to_teleport_away).into(),
 						0,
 						Unlimited,
diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs b/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs
index f509a3a8aca..ca0e81fae42 100644
--- a/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs
+++ b/cumulus/parachains/runtimes/assets/test-utils/src/xcm_helpers.rs
@@ -18,11 +18,10 @@
 
 use xcm::latest::prelude::*;
 
-/// Returns the delivery fees amount for pallet xcm's `teleport_assets` and
-/// `reserve_transfer_assets` extrinsics.
+/// Returns the delivery fees amount for pallet xcm's `teleport_assets` extrinsics.
 /// Because it returns only a `u128`, it assumes delivery fees are only paid
 /// in one asset and that asset is known.
-pub fn transfer_assets_delivery_fees<S: SendXcm>(
+pub fn teleport_assets_delivery_fees<S: SendXcm>(
 	assets: Assets,
 	fee_asset_item: u32,
 	weight_limit: WeightLimit,
diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
index 1e6d485d148..1d404feac3d 100644
--- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
+++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
@@ -57,7 +57,6 @@ use parachains_common::{
 	impls::{AssetsToBlockAuthor, NonZeroIssuance},
 	message_queue::{NarrowOriginToSibling, ParaIdToSibling},
 };
-use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery;
 use smallvec::smallvec;
 use sp_api::impl_runtime_apis;
 pub use sp_consensus_aura::sr25519::AuthorityId as AuraId;
@@ -85,7 +84,7 @@ use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight};
 
 // XCM Imports
 use parachains_common::{AccountId, Signature};
-use xcm::latest::prelude::BodyId;
+use xcm::latest::prelude::{AssetId as AssetLocationId, BodyId};
 
 /// Balance of an account.
 pub type Balance = u128;
@@ -545,6 +544,20 @@ impl pallet_message_queue::Config for Runtime {
 
 impl cumulus_pallet_aura_ext::Config for Runtime {}
 
+parameter_types! {
+	/// The asset ID for the asset that we use to pay for message delivery fees.
+	pub FeeAssetId: AssetLocationId = AssetLocationId(xcm_config::RelayLocation::get());
+	/// The base fee for the message delivery fees (3 CENTS).
+	pub const BaseDeliveryFee: u128 = (1_000_000_000_000u128 / 100).saturating_mul(3);
+}
+
+pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender::ExponentialPrice<
+	FeeAssetId,
+	BaseDeliveryFee,
+	TransactionByteFee,
+	XcmpQueue,
+>;
+
 impl cumulus_pallet_xcmp_queue::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type ChannelInfo = ParachainSystem;
@@ -555,7 +568,7 @@ impl cumulus_pallet_xcmp_queue::Config for Runtime {
 	type ControllerOrigin = EnsureRoot<AccountId>;
 	type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
 	type WeightInfo = ();
-	type PriceForSiblingDelivery = NoPriceForMessageDelivery<ParaId>;
+	type PriceForSiblingDelivery = PriceForSiblingParachainDelivery;
 }
 
 parameter_types! {
diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs
index d83a877c2f8..639bfd95834 100644
--- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs
@@ -28,6 +28,7 @@ use super::{
 	ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee,
 	XcmpQueue,
 };
+use crate::{BaseDeliveryFee, FeeAssetId, TransactionByteFee};
 use core::marker::PhantomData;
 use frame_support::{
 	parameter_types,
@@ -36,10 +37,10 @@ use frame_support::{
 };
 use frame_system::EnsureRoot;
 use pallet_xcm::XcmPassthrough;
-use parachains_common::xcm_config::AssetFeeAsExistentialDepositMultiplier;
+use parachains_common::{xcm_config::AssetFeeAsExistentialDepositMultiplier, TREASURY_PALLET_ID};
 use polkadot_parachain_primitives::primitives::Sibling;
-use polkadot_runtime_common::impls::ToAuthor;
-use sp_runtime::traits::ConvertInto;
+use polkadot_runtime_common::{impls::ToAuthor, xcm_sender::ExponentialPrice};
+use sp_runtime::traits::{AccountIdConversion, ConvertInto};
 use xcm::latest::prelude::*;
 use xcm_builder::{
 	AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
@@ -49,6 +50,7 @@ use xcm_builder::{
 	SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
 	SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit,
 	TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic,
+	XcmFeeManagerFromComponents, XcmFeeToAccount,
 };
 use xcm_executor::{traits::JustTry, XcmExecutor};
 
@@ -59,6 +61,7 @@ parameter_types! {
 	pub const RelayNetwork: Option<NetworkId> = None;
 	pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
 	pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into();
+	pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
 }
 
 /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used
@@ -331,7 +334,10 @@ impl xcm_executor::Config for XcmConfig {
 	type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
 	type AssetLocker = ();
 	type AssetExchanger = ();
-	type FeeManager = ();
+	type FeeManager = XcmFeeManagerFromComponents<
+		(),
+		XcmFeeToAccount<Self::AssetTransactor, AccountId, TreasuryAccount>,
+	>;
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
@@ -355,11 +361,14 @@ pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger =
 /// No local origins on this chain are allowed to dispatch XCM sends/executions.
 pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>;
 
+pub type PriceForParentDelivery =
+	ExponentialPrice<FeeAssetId, BaseDeliveryFee, TransactionByteFee, ParachainSystem>;
+
 /// The means for routing XCM messages which are not for local execution into the right message
 /// queues.
 pub type XcmRouter = WithUniqueTopic<(
 	// Two routers - use UMP to communicate with the relay chain:
-	cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, ()>,
+	cumulus_primitives_utility::ParentAsUmp<ParachainSystem, PolkadotXcm, PriceForParentDelivery>,
 	// ..and XCMP to communicate with the sibling chains.
 	XcmpQueue,
 )>;
diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs
index 0761b375dfb..8a9e5288f2e 100644
--- a/polkadot/xcm/pallet-xcm/src/lib.rs
+++ b/polkadot/xcm/pallet-xcm/src/lib.rs
@@ -1990,6 +1990,7 @@ impl<T: Config> Pallet<T> {
 		]);
 		Ok(Xcm(vec![
 			WithdrawAsset(assets.into()),
+			SetFeesMode { jit_withdraw: true },
 			InitiateReserveWithdraw {
 				assets: Wild(AllCounted(max_assets)),
 				reserve,
diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs
index b29562fc833..2cc228476ba 100644
--- a/polkadot/xcm/pallet-xcm/src/mock.rs
+++ b/polkadot/xcm/pallet-xcm/src/mock.rs
@@ -361,6 +361,10 @@ parameter_types! {
 		0,
 		[Parachain(FOREIGN_ASSET_RESERVE_PARA_ID)]
 	);
+	pub PaidParaForeignReserveLocation: Location = Location::new(
+		0,
+		[Parachain(Para3000::get())]
+	);
 	pub ForeignAsset: Asset = Asset {
 		fun: Fungible(10),
 		id: AssetId(Location::new(
@@ -368,6 +372,13 @@ parameter_types! {
 			[Parachain(FOREIGN_ASSET_RESERVE_PARA_ID), FOREIGN_ASSET_INNER_JUNCTION],
 		)),
 	};
+	pub PaidParaForeignAsset: Asset = Asset {
+		fun: Fungible(10),
+		id: AssetId(Location::new(
+			0,
+			[Parachain(Para3000::get())],
+		)),
+	};
 	pub UsdcReserveLocation: Location = Location::new(
 		0,
 		[Parachain(USDC_RESERVE_PARA_ID)]
@@ -450,6 +461,8 @@ parameter_types! {
 	pub TrustedFilteredTeleport: (AssetFilter, Location) = (FilteredTeleportAsset::get().into(), FilteredTeleportLocation::get());
 	pub TeleportUsdtToForeign: (AssetFilter, Location) = (Usdt::get().into(), ForeignReserveLocation::get());
 	pub TrustedForeign: (AssetFilter, Location) = (ForeignAsset::get().into(), ForeignReserveLocation::get());
+	pub TrustedPaidParaForeign: (AssetFilter, Location) = (PaidParaForeignAsset::get().into(), PaidParaForeignReserveLocation::get());
+
 	pub TrustedUsdc: (AssetFilter, Location) = (Usdc::get().into(), UsdcReserveLocation::get());
 	pub const MaxInstructions: u32 = 100;
 	pub const MaxAssetsIntoHolding: u32 = 64;
@@ -483,7 +496,7 @@ impl xcm_executor::Config for XcmConfig {
 	type XcmSender = XcmRouter;
 	type AssetTransactor = AssetTransactors;
 	type OriginConverter = LocalOriginConverter;
-	type IsReserve = (Case<TrustedForeign>, Case<TrustedUsdc>);
+	type IsReserve = (Case<TrustedForeign>, Case<TrustedUsdc>, Case<TrustedPaidParaForeign>);
 	type IsTeleporter = (
 		Case<TrustedLocal>,
 		Case<TrustedSystemPara>,
diff --git a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs
index 27be5cce145..7752d1355cd 100644
--- a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs
+++ b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs
@@ -2604,3 +2604,174 @@ fn teleport_assets_using_destination_reserve_fee_disallowed() {
 		expected_result,
 	);
 }
+
+/// Test `tested_call` transferring single asset using remote reserve.
+///
+/// Transferring Para3000 asset (`Para3000` reserve) to
+/// `OTHER_PARA_ID` (no teleport trust), therefore triggering remote reserve.
+/// Using the same asset asset (Para3000 reserve) for fees.
+///
+/// Asserts that the sender's balance is decreased and the beneficiary's balance
+/// is increased. Verifies the correct message is sent and event is emitted.
+///
+/// Verifies that XCM router fees (`SendXcm::validate` -> `Assets`) are withdrawn from correct
+/// user account and deposited to a correct target account (`XcmFeesTargetAccount`).
+/// Verifies `expected_result`.
+fn remote_asset_reserve_and_remote_fee_reserve_paid_call<Call>(
+	tested_call: Call,
+	expected_result: DispatchResult,
+) where
+	Call: FnOnce(
+		OriginFor<Test>,
+		Box<VersionedLocation>,
+		Box<VersionedLocation>,
+		Box<VersionedAssets>,
+		u32,
+		WeightLimit,
+	) -> DispatchResult,
+{
+	let weight = BaseXcmWeight::get() * 3;
+	let user_account = AccountId::from(XCM_FEES_NOT_WAIVED_USER_ACCOUNT);
+	let xcm_router_fee_amount = Para3000PaymentAmount::get();
+	let paid_para_id = Para3000::get();
+	let balances = vec![
+		(user_account.clone(), INITIAL_BALANCE),
+		(ParaId::from(paid_para_id).into_account_truncating(), INITIAL_BALANCE),
+		(XcmFeesTargetAccount::get(), INITIAL_BALANCE),
+	];
+	let beneficiary: Location = Junction::AccountId32 { network: None, id: ALICE.into() }.into();
+	new_test_ext_with_balances(balances).execute_with(|| {
+		// create sufficient foreign asset BLA
+		let foreign_initial_amount = 142;
+		let (reserve_location, _, foreign_asset_id_location) = set_up_foreign_asset(
+			paid_para_id,
+			None,
+			user_account.clone(),
+			foreign_initial_amount,
+			true,
+		);
+
+		// transfer destination is another chain that is not the reserve location
+		// the goal is to trigger the remoteReserve case
+		let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap();
+
+		let transferred_asset: Assets = (foreign_asset_id_location.clone(), SEND_AMOUNT).into();
+
+		// balances checks before
+		assert_eq!(
+			AssetsPallet::balance(foreign_asset_id_location.clone(), user_account.clone()),
+			foreign_initial_amount
+		);
+		assert_eq!(Balances::free_balance(user_account.clone()), INITIAL_BALANCE);
+
+		// do the transfer
+		let result = tested_call(
+			RuntimeOrigin::signed(user_account.clone()),
+			Box::new(dest.clone().into()),
+			Box::new(beneficiary.clone().into()),
+			Box::new(transferred_asset.into()),
+			0 as u32,
+			Unlimited,
+		);
+		assert_eq!(result, expected_result);
+		if expected_result.is_err() {
+			// short-circuit here for tests where we expect failure
+			return;
+		}
+
+		let mut last_events = last_events(7).into_iter();
+		// asset events
+		// forceCreate
+		last_events.next().unwrap();
+		// mint tokens
+		last_events.next().unwrap();
+		// burn tokens
+		last_events.next().unwrap();
+		// balance events
+		// burn delivery fee
+		last_events.next().unwrap();
+		// mint delivery fee
+		last_events.next().unwrap();
+		assert_eq!(
+			last_events.next().unwrap(),
+			RuntimeEvent::XcmPallet(crate::Event::Attempted {
+				outcome: Outcome::Complete { used: weight }
+			})
+		);
+
+		// user account spent (transferred) amount
+		assert_eq!(
+			AssetsPallet::balance(foreign_asset_id_location.clone(), user_account.clone()),
+			foreign_initial_amount - SEND_AMOUNT
+		);
+
+		// user account spent delivery fees
+		assert_eq!(Balances::free_balance(user_account), INITIAL_BALANCE - xcm_router_fee_amount);
+
+		// XcmFeesTargetAccount where should lend xcm_router_fee_amount
+		assert_eq!(
+			Balances::free_balance(XcmFeesTargetAccount::get()),
+			INITIAL_BALANCE + xcm_router_fee_amount
+		);
+
+		// Verify total and active issuance of foreign BLA have decreased (burned on
+		// reserve-withdraw)
+		let expected_issuance = foreign_initial_amount - SEND_AMOUNT;
+		assert_eq!(
+			AssetsPallet::total_issuance(foreign_asset_id_location.clone()),
+			expected_issuance
+		);
+		assert_eq!(
+			AssetsPallet::active_issuance(foreign_asset_id_location.clone()),
+			expected_issuance
+		);
+
+		let context = UniversalLocation::get();
+		let foreign_id_location_reanchored =
+			foreign_asset_id_location.reanchored(&dest, &context).unwrap();
+		let dest_reanchored = dest.reanchored(&reserve_location, &context).unwrap();
+
+		// Verify sent XCM program
+		assert_eq!(
+			sent_xcm(),
+			vec![(
+				reserve_location,
+				// `assets` are burned on source and withdrawn from SA in remote reserve chain
+				Xcm(vec![
+					WithdrawAsset((Location::here(), SEND_AMOUNT).into()),
+					ClearOrigin,
+					buy_execution((Location::here(), SEND_AMOUNT / 2)),
+					DepositReserveAsset {
+						assets: Wild(AllCounted(1)),
+						// final destination is `dest` as seen by `reserve`
+						dest: dest_reanchored,
+						// message sent onward to `dest`
+						xcm: Xcm(vec![
+							buy_execution((foreign_id_location_reanchored, SEND_AMOUNT / 2)),
+							DepositAsset { assets: AllCounted(1).into(), beneficiary }
+						])
+					}
+				])
+			)]
+		);
+	});
+}
+/// Test `transfer_assets` with remote asset reserve and remote fee reserve.
+#[test]
+fn transfer_assets_with_remote_asset_reserve_and_remote_asset_fee_reserve_paid_works() {
+	let expected_result = Ok(());
+	remote_asset_reserve_and_remote_fee_reserve_paid_call(
+		XcmPallet::transfer_assets,
+		expected_result,
+	);
+}
+/// Test `limited_reserve_transfer_assets` with remote asset reserve and remote fee reserve.
+#[test]
+fn limited_reserve_transfer_assets_with_remote_asset_reserve_and_remote_asset_fee_reserve_paid_works(
+) {
+	let expected_result = Ok(());
+	remote_asset_reserve_and_remote_fee_reserve_paid_call(
+		XcmPallet::limited_reserve_transfer_assets,
+		expected_result,
+	);
+}
diff --git a/prdoc/pr_3792.prdoc b/prdoc/pr_3792.prdoc
new file mode 100644
index 00000000000..cbcdc29a9c6
--- /dev/null
+++ b/prdoc/pr_3792.prdoc
@@ -0,0 +1,19 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: "[pallet-xcm] fix transport fees for remote reserve transfers"
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      This PR fixes `pallet_xcm::transfer_assets` and
+      `pallet_xcm::limited_reserve_transfer_assets` extrinsics for transfers
+      that need to go through remote reserves. The fix is adding a
+      `SetFeesMode { jit_withdraw: true }` instruction before local execution of
+      `InitiateReserveWithdraw` so that delivery fees are correctly charged by
+      the xcm-executor. Without this change, a runtime that has implemented
+      delivery fees would not be able to execute remote reserve transfers using
+      these extrinsics.
+
+crates:
+  - name: pallet-xcm
-- 
GitLab