Skip to content

Commit

Permalink
Assets in pool with native can be used in query_weight_to_asset_fee (
Browse files Browse the repository at this point in the history
…paritytech#6080)

A follow-up to paritytech#5599.
Assets in a pool with the native one are returned from
`query_acceptable_payment_assets`. Now those assets can be used in
`query_weight_to_asset_fee` to get the correct amount that needs to be
paid.

---------

Co-authored-by: command-bot <>
  • Loading branch information
franciscoaguirre authored and mordamax committed Oct 29, 2024
1 parent 2896295 commit 1fcadfa
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 37 deletions.
68 changes: 68 additions & 0 deletions cumulus/parachains/integration-tests/emulated/common/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ mod imports {
pub use emulated_integration_tests_common::{
accounts::DUMMY_EMPTY,
test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay,
test_relay_is_trusted_teleporter,
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ mod imports {
pub use emulated_integration_tests_common::{
accounts::DUMMY_EMPTY,
test_parachain_is_trusted_teleporter, test_parachain_is_trusted_teleporter_for_relay,
test_relay_is_trusted_teleporter,
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
44 changes: 27 additions & 17 deletions cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,31 +1412,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:?}!");
Expand Down
47 changes: 29 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 @@ -1450,31 +1450,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:?}!");
Expand Down
33 changes: 33 additions & 0 deletions cumulus/parachains/runtimes/assets/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down
22 changes: 22 additions & 0 deletions prdoc/pr_6080.prdoc
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 1fcadfa

Please sign in to comment.