From a6df7c7b80b6923ec8c7fd4391eac93ce0c4f55e Mon Sep 17 00:00:00 2001
From: "paritytech-cmd-bot-polkadot-sdk[bot]"
 <179002856+paritytech-cmd-bot-polkadot-sdk[bot]@users.noreply.github.com>
Date: Tue, 12 Nov 2024 11:53:27 +0100
Subject: [PATCH] [stable2409] Backport #6080 (#6181)

Backport #6080 into `stable2409` from franciscoaguirre.

See the
[documentation](https://github.com/paritytech/polkadot-sdk/blob/master/docs/BACKPORT.md)
on how to use this bot.

<!--
  # To be used by other automation, do not modify:
  original-pr-number: #${pull_number}
-->

---------

Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
Co-authored-by: Egor_P <egor@parity.io>
---
 .../emulated/common/src/macros.rs             | 68 +++++++++++++++++++
 .../tests/assets/asset-hub-rococo/src/lib.rs  |  5 +-
 .../assets/asset-hub-rococo/src/tests/swap.rs |  5 ++
 .../tests/assets/asset-hub-westend/src/lib.rs |  5 +-
 .../asset-hub-westend/src/tests/swap.rs       |  5 ++
 .../assets/asset-hub-rococo/src/lib.rs        | 44 +++++++-----
 .../assets/asset-hub-westend/src/lib.rs       | 47 ++++++++-----
 .../runtimes/assets/common/src/lib.rs         | 33 +++++++++
 prdoc/pr_6181.prdoc                           | 22 ++++++
 9 files changed, 195 insertions(+), 39 deletions(-)
 create mode 100644 prdoc/pr_6181.prdoc

diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs
index 68926b04bfe..3ff5ed388a3 100644
--- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs
+++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs
@@ -451,3 +451,71 @@ macro_rules! test_dry_run_transfer_across_pk_bridge {
 		}
 	};
 }
+
+#[macro_export]
+macro_rules! test_xcm_fee_querying_apis_work_for_asset_hub {
+	( $asset_hub:ty ) => {
+		$crate::macros::paste::paste! {
+			use emulated_integration_tests_common::USDT_ID;
+			use xcm_runtime_apis::fees::{Error as XcmPaymentApiError, runtime_decl_for_xcm_payment_api::XcmPaymentApiV1};
+
+			$asset_hub::execute_with(|| {
+				// Setup a pool between USDT and WND.
+				type RuntimeOrigin = <$asset_hub as Chain>::RuntimeOrigin;
+				type Assets = <$asset_hub as [<$asset_hub Pallet>]>::Assets;
+				type AssetConversion = <$asset_hub as [<$asset_hub Pallet>]>::AssetConversion;
+				let wnd = Location::new(1, []);
+				let usdt = Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(USDT_ID.into())]);
+				let sender = [<$asset_hub Sender>]::get();
+				assert_ok!(AssetConversion::create_pool(
+					RuntimeOrigin::signed(sender.clone()),
+					Box::new(wnd.clone()),
+					Box::new(usdt.clone()),
+				));
+
+				type Runtime = <$asset_hub as Chain>::Runtime;
+				let acceptable_payment_assets = Runtime::query_acceptable_payment_assets(4).unwrap();
+				assert_eq!(acceptable_payment_assets, vec![
+					VersionedAssetId::from(AssetId(wnd.clone())),
+					VersionedAssetId::from(AssetId(usdt.clone())),
+				]);
+
+				let program = Xcm::<()>::builder()
+					.withdraw_asset((Parent, 100u128))
+					.buy_execution((Parent, 10u128), Unlimited)
+					.deposit_asset(All, [0u8; 32])
+					.build();
+				let weight = Runtime::query_xcm_weight(VersionedXcm::from(program)).unwrap();
+				let fee_in_wnd = Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::from(AssetId(wnd.clone()))).unwrap();
+				// Assets not in a pool don't work.
+				assert!(Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::from(AssetId(Location::new(0, [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(1)])))).is_err());
+				let fee_in_usdt_fail = Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::from(AssetId(usdt.clone())));
+				// Weight to asset fee fails because there's not enough asset in the pool.
+				// We just created it, there's none.
+				assert_eq!(fee_in_usdt_fail, Err(XcmPaymentApiError::AssetNotFound));
+				// We add some.
+				assert_ok!(Assets::mint(
+					RuntimeOrigin::signed(sender.clone()),
+					USDT_ID.into(),
+					sender.clone().into(),
+					5_000_000_000_000
+				));
+				// We make 1 WND = 4 USDT.
+				assert_ok!(AssetConversion::add_liquidity(
+					RuntimeOrigin::signed(sender.clone()),
+					Box::new(wnd),
+					Box::new(usdt.clone()),
+					1_000_000_000_000,
+					4_000_000_000_000,
+					0,
+					0,
+					sender.into()
+				));
+				// Now it works.
+				let fee_in_usdt = Runtime::query_weight_to_asset_fee(weight, VersionedAssetId::from(AssetId(usdt)));
+				assert_ok!(fee_in_usdt);
+				assert!(fee_in_usdt.unwrap() > fee_in_wnd);
+			});
+		}
+	};
+}
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 0e43108a417..706102a3067 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
@@ -36,8 +36,9 @@ mod imports {
 	pub use asset_test_utils::xcm_helpers;
 	pub use emulated_integration_tests_common::{
 		accounts::DUMMY_EMPTY,
-		get_account_id_from_seed, test_parachain_is_trusted_teleporter,
-		test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter,
+		get_account_id_from_seed,
+		test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay,
+		test_relay_is_trusted_teleporter, test_xcm_fee_querying_apis_work_for_asset_hub,
 		xcm_emulator::{
 			assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test,
 			TestArgs, TestContext, TestExt,
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs
index ac0c90ba198..d9b32eaa357 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs
@@ -386,3 +386,8 @@ fn pay_xcm_fee_with_some_asset_swapped_for_native() {
 		);
 	});
 }
+
+#[test]
+fn xcm_fee_querying_apis_work() {
+	test_xcm_fee_querying_apis_work_for_asset_hub!(AssetHubRococo);
+}
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 d0016b5a1b1..2548f6a23de 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
@@ -33,8 +33,9 @@ mod imports {
 	pub use asset_test_utils::xcm_helpers;
 	pub use emulated_integration_tests_common::{
 		accounts::DUMMY_EMPTY,
-		get_account_id_from_seed, test_parachain_is_trusted_teleporter,
-		test_parachain_is_trusted_teleporter_for_relay, test_relay_is_trusted_teleporter,
+		get_account_id_from_seed,
+		test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay,
+		test_relay_is_trusted_teleporter, test_xcm_fee_querying_apis_work_for_asset_hub,
 		xcm_emulator::{
 			assert_expected_events, bx, Chain, Parachain as Para, RelayChain as Relay, Test,
 			TestArgs, TestContext, TestExt,
diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs
index 1a282145215..4535fd43199 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs
@@ -389,3 +389,8 @@ fn pay_xcm_fee_with_some_asset_swapped_for_native() {
 		);
 	});
 }
+
+#[test]
+fn xcm_fee_querying_apis_work() {
+	test_xcm_fee_querying_apis_work_for_asset_hub!(AssetHubWestend);
+}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
index d1199c79ec0..96dbc9c0a18 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
@@ -1329,31 +1329,41 @@ impl_runtime_apis! {
 			// We accept the native token to pay fees.
 			let mut acceptable_assets = vec![AssetId(native_token.clone())];
 			// We also accept all assets in a pool with the native token.
-			acceptable_assets.extend(
-				pallet_asset_conversion::Pools::<Runtime>::iter_keys().filter_map(
-					|(asset_1, asset_2)| {
-						if asset_1 == native_token {
-							Some(asset_2.clone().into())
-						} else if asset_2 == native_token {
-							Some(asset_1.clone().into())
-						} else {
-							None
-						}
-					},
-				),
-			);
+			let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::<
+				Runtime,
+				xcm::v4::Location
+			>(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter();
+			acceptable_assets.extend(assets_in_pool_with_native);
 			PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets)
 		}
 
 		fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> {
+			let native_asset = xcm_config::TokenLocation::get();
+			let fee_in_native = WeightToFee::weight_to_fee(&weight);
 			match asset.try_as::<AssetId>() {
-				Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => {
+				Ok(asset_id) if asset_id.0 == native_asset => {
 					// for native token
-					Ok(WeightToFee::weight_to_fee(&weight))
+					Ok(fee_in_native)
 				},
 				Ok(asset_id) => {
-					log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!");
-					Err(XcmPaymentApiError::AssetNotFound)
+					let assets_in_pool_with_this_asset: Vec<_> = assets_common::get_assets_in_pool_with::<
+						Runtime,
+						xcm::v4::Location
+					>(&asset_id.0).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?;
+					if assets_in_pool_with_this_asset
+						.into_iter()
+						.map(|asset_id| asset_id.0)
+						.any(|location| location == native_asset) {
+						pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens(
+							asset_id.clone().0,
+							native_asset,
+							fee_in_native,
+							true, // We include the fee.
+						).ok_or(XcmPaymentApiError::AssetNotFound)
+					} else {
+						log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!");
+						Err(XcmPaymentApiError::AssetNotFound)
+					}
 				},
 				Err(_) => {
 					log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - failed to convert asset: {asset:?}!");
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index 5e22604ee72..19baa921ff2 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -1362,31 +1362,42 @@ impl_runtime_apis! {
 			// We accept the native token to pay fees.
 			let mut acceptable_assets = vec![AssetId(native_token.clone())];
 			// We also accept all assets in a pool with the native token.
-			acceptable_assets.extend(
-				pallet_asset_conversion::Pools::<Runtime>::iter_keys().filter_map(
-					|(asset_1, asset_2)| {
-						if asset_1 == native_token {
-							Some(asset_2.clone().into())
-						} else if asset_2 == native_token {
-							Some(asset_1.clone().into())
-						} else {
-							None
-						}
-					},
-				),
-			);
+			let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::<
+				Runtime,
+				xcm::v4::Location
+			>(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter();
+			acceptable_assets.extend(assets_in_pool_with_native);
 			PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets)
 		}
 
 		fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> {
+			let native_asset = xcm_config::WestendLocation::get();
+			let fee_in_native = WeightToFee::weight_to_fee(&weight);
 			match asset.try_as::<AssetId>() {
-				Ok(asset_id) if asset_id.0 == xcm_config::WestendLocation::get() => {
-					// for native token
-					Ok(WeightToFee::weight_to_fee(&weight))
+				Ok(asset_id) if asset_id.0 == native_asset => {
+					// for native asset
+					Ok(fee_in_native)
 				},
 				Ok(asset_id) => {
-					log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!");
-					Err(XcmPaymentApiError::AssetNotFound)
+					// We recognize assets in a pool with the native one.
+					let assets_in_pool_with_this_asset: Vec<_> = assets_common::get_assets_in_pool_with::<
+						Runtime,
+						xcm::v4::Location
+					>(&asset_id.0).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?;
+					if assets_in_pool_with_this_asset
+						.into_iter()
+						.map(|asset_id| asset_id.0)
+						.any(|location| location == native_asset) {
+						pallet_asset_conversion::Pallet::<Runtime>::quote_price_tokens_for_exact_tokens(
+							asset_id.clone().0,
+							native_asset,
+							fee_in_native,
+							true, // We include the fee.
+						).ok_or(XcmPaymentApiError::AssetNotFound)
+					} else {
+						log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!");
+						Err(XcmPaymentApiError::AssetNotFound)
+					}
 				},
 				Err(_) => {
 					log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - failed to convert asset: {asset:?}!");
diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs
index deda5fa4ab9..26046e5974b 100644
--- a/cumulus/parachains/runtimes/assets/common/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs
@@ -26,6 +26,9 @@ pub mod runtime_api;
 extern crate alloc;
 
 use crate::matching::{LocalLocationPattern, ParentLocation};
+use alloc::vec::Vec;
+use codec::{Decode, EncodeLike};
+use core::cmp::PartialEq;
 use frame_support::traits::{Equals, EverythingBut};
 use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId};
 use sp_runtime::traits::TryConvertInto;
@@ -134,6 +137,36 @@ pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> =
 		TryConvertInto,
 	>;
 
+/// Returns an iterator of all assets in a pool with `asset`.
+///
+/// Should only be used in runtime APIs since it iterates over the whole
+/// `pallet_asset_conversion::Pools` map.
+///
+/// It takes in any version of an XCM Location but always returns the latest one.
+/// This is to allow some margin of migrating the pools when updating the XCM version.
+///
+/// An error of type `()` is returned if the version conversion fails for XCM locations.
+/// This error should be mapped by the caller to a more descriptive one.
+pub fn get_assets_in_pool_with<
+	Runtime: pallet_asset_conversion::Config<PoolId = (L, L)>,
+	L: TryInto<Location> + Clone + Decode + EncodeLike + PartialEq,
+>(
+	asset: &L,
+) -> Result<Vec<AssetId>, ()> {
+	pallet_asset_conversion::Pools::<Runtime>::iter_keys()
+		.filter_map(|(asset_1, asset_2)| {
+			if asset_1 == *asset {
+				Some(asset_2)
+			} else if asset_2 == *asset {
+				Some(asset_1)
+			} else {
+				None
+			}
+		})
+		.map(|location| location.try_into().map_err(|_| ()).map(AssetId))
+		.collect::<Result<Vec<_>, _>>()
+}
+
 #[cfg(test)]
 mod tests {
 	use super::*;
diff --git a/prdoc/pr_6181.prdoc b/prdoc/pr_6181.prdoc
new file mode 100644
index 00000000000..52ecd58dddd
--- /dev/null
+++ b/prdoc/pr_6181.prdoc
@@ -0,0 +1,22 @@
+# 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: Assets in pool with native can be used in query_weight_to_asset_fee in Asset Hubs
+
+doc:
+  - audience: Runtime User
+    description: |
+      `query_weight_to_asset_fee` now works with assets in a pool with the native asset in both
+      Westend and Rococo asset hubs.
+      This means all the information you get from `query_acceptable_payment_assets` can be used
+      directly in `query_weight_to_asset_fee` to get the correct fees that need to be paid.
+
+crates:
+  - name: assets-common
+    bump: minor
+  - name: asset-hub-westend-runtime
+    bump: minor
+  - name: asset-hub-rococo-runtime
+    bump: minor
+  - name: emulated-integration-tests-common
+    bump: minor
-- 
GitLab