Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable2407] Backport #6459 #6663

Open
wants to merge 1 commit into
base: stable2407
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ bp-bridge-hub-westend.workspace = true
snowbridge-router-primitives.workspace = true

[dev-dependencies]
asset-test-utils = { default-features = true, path = "../test-utils" }
asset-test-utils = { workspace = true, default-features = true }
parachains-runtimes-test-utils = { workspace = true, default-features = true }

[build-dependencies]
substrate-wasm-builder = { optional = true, workspace = true, default-features = true }
Expand Down
35 changes: 19 additions & 16 deletions cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1327,35 +1327,38 @@ impl_runtime_apis! {
impl xcm_runtime_apis::fees::XcmPaymentApi<Block> for Runtime {
fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
let native_token = xcm_config::TokenLocation::get();
let native_token_v3 = xcm_config::TokenLocationV3::get();
// 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_v3 {
Some(AssetId(asset_2.try_into().unwrap()))
} else if asset_2 == native_token_v3 {
Some(AssetId(asset_1.try_into().unwrap()))
} else {
None
}
},
),
assets_common::PoolAdapter::<Runtime>::get_assets_in_pool_with(native_token)
.map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?
);
PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets)
}

fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> {
match asset.try_as::<AssetId>() {
Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => {
let native_asset = xcm_config::TokenLocation::get();
let fee_in_native = WeightToFee::weight_to_fee(&weight);
let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into();
match latest_asset_id {
Ok(asset_id) if asset_id.0 == native_asset => {
// for native token
Ok(WeightToFee::weight_to_fee(&weight))
},
Ok(asset_id) => {
log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!");
Err(XcmPaymentApiError::AssetNotFound)
// Try to get current price of `asset_id` in `native_asset`.
if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::<Runtime>::quote_price_tokens_for_exact_tokens(
asset_id.0.clone(),
native_asset,
fee_in_native,
true, // We include the fee.
) {
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)
}
},
Err(_) => {
log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - failed to convert asset: {asset:?}!");
Expand Down
24 changes: 20 additions & 4 deletions cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ use asset_hub_rococo_runtime::{
ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf,
LocationToAccountId, StakingPot, TokenLocation, TrustBackedAssetsPalletLocation, XcmConfig,
},
AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection,
ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase,
MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
SessionKeys, ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, Block,
CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance,
MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall,
RuntimeEvent, RuntimeOrigin, SessionKeys, ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
};
use asset_test_utils::{
test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys,
Expand Down Expand Up @@ -1473,3 +1473,19 @@ fn change_xcm_bridge_hub_ethereum_base_fee_by_governance_works() {
},
)
}

#[test]
fn xcm_payment_api_works() {
parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::<
Runtime,
RuntimeCall,
RuntimeOrigin,
Block,
>();
asset_test_utils::test_cases::xcm_payment_api_with_pools_works::<
Runtime,
RuntimeCall,
RuntimeOrigin,
Block,
>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ bp-bridge-hub-rococo.workspace = true
bp-bridge-hub-westend.workspace = true

[dev-dependencies]
asset-test-utils = { default-features = true, path = "../test-utils" }
asset-test-utils = { workspace = true, default-features = true }
parachains-runtimes-test-utils = { workspace = true, default-features = true }

[build-dependencies]
substrate-wasm-builder = { optional = true, workspace = true, default-features = true }
Expand Down
39 changes: 21 additions & 18 deletions cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1355,35 +1355,38 @@ impl_runtime_apis! {
impl xcm_runtime_apis::fees::XcmPaymentApi<Block> for Runtime {
fn query_acceptable_payment_assets(xcm_version: xcm::Version) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
let native_token = xcm_config::WestendLocation::get();
let native_token_v3 = xcm_config::WestendLocationV3::get();
// 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_v3 {
Some(AssetId(asset_2.try_into().unwrap()))
} else if asset_2 == native_token_v3 {
Some(AssetId(asset_1.try_into().unwrap()))
} else {
None
}
},
),
assets_common::PoolAdapter::<Runtime>::get_assets_in_pool_with(native_token)
.map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?
);
PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets)
}

fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result<u128, XcmPaymentApiError> {
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))
let native_asset = xcm_config::WestendLocation::get();
let fee_in_native = WeightToFee::weight_to_fee(&weight);
let latest_asset_id: Result<AssetId, ()> = asset.clone().try_into();
match latest_asset_id {
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)
// Try to get current price of `asset_id` in `native_asset`.
if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::<Runtime>::quote_price_tokens_for_exact_tokens(
asset_id.0.clone(),
native_asset,
fee_in_native,
true, // We include the fee.
) {
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)
}
},
Err(_) => {
log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - failed to convert asset: {asset:?}!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use asset_hub_westend_runtime::{
LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, WestendLocation,
XcmConfig,
},
AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets,
AllPalletsWithoutSystem, Assets, Balances, Block, ExistentialDeposit, ForeignAssets,
ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem,
PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys,
ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue,
Expand Down Expand Up @@ -1419,3 +1419,19 @@ fn reserve_transfer_native_asset_to_non_teleport_para_works() {
WeightLimit::Unlimited,
);
}

#[test]
fn xcm_payment_api_works() {
parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::<
Runtime,
RuntimeCall,
RuntimeOrigin,
Block,
>();
asset_test_utils::test_cases::xcm_payment_api_with_pools_works::<
Runtime,
RuntimeCall,
RuntimeOrigin,
Block,
>();
}
72 changes: 71 additions & 1 deletion cumulus/parachains/runtimes/assets/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ pub mod runtime_api;
extern crate alloc;

use crate::matching::{LocalLocationPattern, ParentLocation};
use alloc::vec::Vec;
use codec::{Decode, EncodeLike};
use core::{cmp::PartialEq, marker::PhantomData};
use frame_support::traits::{Equals, EverythingBut};
use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId};
use sp_runtime::traits::TryConvertInto;
use xcm::latest::Location;
use xcm::latest::{AssetId, Location};
use xcm_builder::{
AsPrefixedGeneralIndex, MatchedConvertedConcreteId, StartsWith, WithLatestLocationConverter,
};
Expand Down Expand Up @@ -134,6 +137,73 @@ pub type PoolAssetsConvertedConcreteId<PoolAssetsPalletLocation, Balance> =
TryConvertInto,
>;

/// 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<Runtime>(PhantomData<Runtime>);
impl<
Runtime: pallet_asset_conversion::Config<PoolId = (L, L), AssetKind = L>,
L: TryFrom<Location> + TryInto<Location> + Clone + Decode + EncodeLike + PartialEq,
> PoolAdapter<Runtime>
{
/// 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<Vec<AssetId>, ()> {
// 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::<Result<Vec<_>, _>>()
}

/// Provides a current prices. Wrapper over
/// `pallet_asset_conversion::Pallet::<T>::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<Option<Runtime::Balance>, ()> {
// 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::<Runtime>::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<Item = L> + '_ {
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
}
})
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading
Loading