From f753a7de294663bd67f459af8b0f21203fa2c6de Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 13 Nov 2024 21:30:57 +0100 Subject: [PATCH] Simplify `XcmPaymentApi` with `PoolAdapter` (#6467) Co-authored-by: GitHub Action Co-authored-by: Francisco Aguirre --- .../assets/asset-hub-rococo/src/lib.rs | 25 +++--- .../assets/asset-hub-westend/src/lib.rs | 26 +++--- .../runtimes/assets/common/src/lib.rs | 79 ++++++++++++++----- prdoc/pr_6459.prdoc | 2 + 4 files changed, 78 insertions(+), 54 deletions(-) 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 d76dac9564b4..a7b3d4ee80c7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1412,11 +1412,10 @@ 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. - let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter(); - acceptable_assets.extend(assets_in_pool_with_native); + acceptable_assets.extend( + assets_common::PoolAdapter::::get_assets_in_pool_with(native_token) + .map_err(|()| XcmPaymentApiError::VersionedConversionFailed)? + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } @@ -1430,20 +1429,14 @@ impl_runtime_apis! { Ok(fee_in_native) }, Ok(asset_id) => { - let assets_in_pool_with_this_asset: Vec<_> = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::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::::quote_price_tokens_for_exact_tokens( - asset_id.clone().0, + // Try to get current price of `asset_id` in `native_asset`. + if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::::quote_price_tokens_for_exact_tokens( + asset_id.0.clone(), native_asset, fee_in_native, true, // We include the fee. - ).ok_or(XcmPaymentApiError::AssetNotFound) + ) { + Ok(swapped_in_native) } else { log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); Err(XcmPaymentApiError::AssetNotFound) 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 6661f0b2dc57..8c683606b572 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1528,11 +1528,10 @@ 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. - let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter(); - acceptable_assets.extend(assets_in_pool_with_native); + acceptable_assets.extend( + assets_common::PoolAdapter::::get_assets_in_pool_with(native_token) + .map_err(|()| XcmPaymentApiError::VersionedConversionFailed)? + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } @@ -1546,21 +1545,14 @@ impl_runtime_apis! { Ok(fee_in_native) }, Ok(asset_id) => { - // 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::v5::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::::quote_price_tokens_for_exact_tokens( - asset_id.clone().0, + // Try to get current price of `asset_id` in `native_asset`. + if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::::quote_price_tokens_for_exact_tokens( + asset_id.0.clone(), native_asset, fee_in_native, true, // We include the fee. - ).ok_or(XcmPaymentApiError::AssetNotFound) + ) { + Ok(swapped_in_native) } else { log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); Err(XcmPaymentApiError::AssetNotFound) diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 1d2d45b42c5d..25c2df6b68d1 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -28,7 +28,7 @@ extern crate alloc; use crate::matching::{LocalLocationPattern, ParentLocation}; use alloc::vec::Vec; use codec::{Decode, EncodeLike}; -use core::cmp::PartialEq; +use core::{cmp::PartialEq, marker::PhantomData}; use frame_support::traits::{Equals, EverythingBut}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; use sp_runtime::traits::TryConvertInto; @@ -137,24 +137,62 @@ pub type PoolAssetsConvertedConcreteId = 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, - L: TryInto + Clone + Decode + EncodeLike + PartialEq, ->( - asset: &L, -) -> Result, ()> { - pallet_asset_conversion::Pools::::iter_keys() - .filter_map(|(asset_1, asset_2)| { +/// Adapter implementation for accessing pools (`pallet_asset_conversion`) that uses `AssetKind` as +/// a `xcm::v*` which could be different from the `xcm::latest`. +pub struct PoolAdapter(PhantomData); +impl< + Runtime: pallet_asset_conversion::Config, + L: TryFrom + TryInto + Clone + Decode + EncodeLike + PartialEq, + > PoolAdapter +{ + /// Returns a vector 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(asset: Location) -> Result, ()> { + // convert latest to the `L` version. + let asset: L = asset.try_into().map_err(|_| ())?; + Self::iter_assets_in_pool_with(&asset) + .map(|location| { + // convert `L` to the latest `AssetId` + location.try_into().map_err(|_| ()).map(AssetId) + }) + .collect::, _>>() + } + + /// Provides a current prices. Wrapper over + /// `pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens`. + /// + /// 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 quote_price_tokens_for_exact_tokens( + asset_1: Location, + asset_2: Location, + amount: Runtime::Balance, + include_fees: bool, + ) -> Result, ()> { + // Convert latest to the `L` version. + let asset_1: L = asset_1.try_into().map_err(|_| ())?; + let asset_2: L = asset_2.try_into().map_err(|_| ())?; + + // Quote swap price. + Ok(pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( + asset_1, + asset_2, + amount, + include_fees, + )) + } + + /// Helper function for filtering pool. + pub fn iter_assets_in_pool_with(asset: &L) -> impl Iterator + '_ { + pallet_asset_conversion::Pools::::iter_keys().filter_map(|(asset_1, asset_2)| { if asset_1 == *asset { Some(asset_2) } else if asset_2 == *asset { @@ -163,8 +201,7 @@ pub fn get_assets_in_pool_with< None } }) - .map(|location| location.try_into().map_err(|_| ()).map(AssetId)) - .collect::, _>>() + } } #[cfg(test)] diff --git a/prdoc/pr_6459.prdoc b/prdoc/pr_6459.prdoc index 8e1ac9efef82..592ba4c6b29d 100644 --- a/prdoc/pr_6459.prdoc +++ b/prdoc/pr_6459.prdoc @@ -18,3 +18,5 @@ crates: bump: patch - name: xcm-runtime-apis bump: patch + - name: assets-common + bump: patch