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

[stable2409] Backport #6080 #6181

Merged
merged 4 commits into from
Nov 12, 2024
Merged
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
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 @@ -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,
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 @@ -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,
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 @@ -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:?}!");
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 @@ -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:?}!");
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_6181.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
Loading