diff --git a/pallets/pallet-asset-switch/src/lib.rs b/pallets/pallet-asset-switch/src/lib.rs index ffc63f9bc7..9944d92b28 100644 --- a/pallets/pallet-asset-switch/src/lib.rs +++ b/pallets/pallet-asset-switch/src/lib.rs @@ -39,14 +39,7 @@ mod benchmarking; #[cfg(feature = "runtime-benchmarks")] pub use benchmarking::{BenchmarkHelper, PartialBenchmarkInfo}; -use ::xcm::{ - v4::{ - Asset, AssetFilter, AssetId, - Instruction::{BuyExecution, DepositAsset, RefundSurplus, ReportHolding, SetAppendix, WithdrawAsset}, - Location, QueryResponseInfo, Weight, WeightLimit, WildAsset, WildFungibility, Xcm, - }, - VersionedAsset, VersionedAssetId, VersionedLocation, -}; +use ::xcm::{VersionedAsset, VersionedAssetId, VersionedLocation}; use frame_support::traits::{ fungible::Inspect, tokens::{Fortitude, Preservation}, @@ -54,7 +47,7 @@ use frame_support::traits::{ }; use parity_scale_codec::{Decode, Encode}; use sp_runtime::traits::{TrailingZeroInput, Zero}; -use sp_std::{boxed::Box, vec}; +use sp_std::boxed::Box; pub use crate::pallet::*; @@ -63,7 +56,7 @@ const LOG_TARGET: &str = "runtime::pallet-asset-switch"; #[frame_support::pallet] pub mod pallet { use crate::{ - switch::{NewSwitchPairInfo, SwitchPairInfo, SwitchPairInfoV4, SwitchPairStatus, UnconfirmedSwitchInfo}, + switch::{NewSwitchPairInfo, SwitchPairInfo, SwitchPairStatus, UnconfirmedSwitchInfo}, traits::SwitchHooks, WeightInfo, LOG_TARGET, }; @@ -80,7 +73,12 @@ pub mod pallet { use sp_runtime::traits::{TryConvert, Zero}; use sp_std::{boxed::Box, vec}; use xcm::{ - v4::{validate_send, InteriorLocation, Junction, Location, QueryId, SendXcm}, + v4::{ + validate_send, Asset, AssetFilter, AssetId, + Instruction::{BuyExecution, DepositAsset, RefundSurplus, ReportHolding, SetAppendix, WithdrawAsset}, + InteriorLocation, Junction, Location, QueryId, QueryResponseInfo, SendXcm, WeightLimit, WildAsset, + WildFungibility, Xcm, + }, VersionedAsset, VersionedAssetId, VersionedLocation, }; use xcm_executor::traits::TransactAsset; @@ -481,7 +479,33 @@ pub mod pallet { Error::::Liquidity ); - let switch_pair_v4 = SwitchPairInfoV4::<_, T, I>::try_from(switch_pair).map_err(DispatchError::from)?; + let asset_id_v4: AssetId = switch_pair.remote_asset_id.clone().try_into().map_err(|e| { + log::error!( + target: LOG_TARGET, + "Failed to convert asset ID {:?} into v4 `AssetId` with error {:?}", + switch_pair.remote_asset_id, + e + ); + DispatchError::from(Error::::Internal) + })?; + let remote_asset_fee_v4: Asset = switch_pair.remote_xcm_fee.clone().try_into().map_err(|e| { + log::error!( + target: LOG_TARGET, + "Failed to convert remote XCM asset fee {:?} into v4 `Asset` with error {:?}", + switch_pair.remote_xcm_fee, + e + ); + DispatchError::from(Error::::Xcm) + })?; + let destination_v4: Location = switch_pair.remote_reserve_location.clone().try_into().map_err(|e| { + log::error!( + target: LOG_TARGET, + "Failed to convert remote reserve location {:?} into v4 `Location` with error {:?}", + switch_pair.remote_reserve_location, + e + ); + DispatchError::from(Error::::Internal) + })?; let beneficiary_v4: Location = (*beneficiary.clone()).try_into().map_err(|e| { log::info!( target: LOG_TARGET, @@ -506,36 +530,97 @@ pub mod pallet { })?; // 7. Compose and validate XCM message + let query_id = NextQueryId::::get(); let universal_location = T::UniversalLocation::get(); - let our_location_for_destination = universal_location - .invert_target(&switch_pair_v4.remote_reserve_location) - .map_err(|e| { - log::error!( - target: LOG_TARGET, - "Failed to invert universal location {:?} for destination {:?} with error {:?}", - universal_location, switch_pair_v4.remote_reserve_location, e + let our_location_for_destination = universal_location.invert_target(&destination_v4).map_err(|e| { + log::error!( + target: LOG_TARGET, + "Failed to invert universal location {:?} for destination {:?} with error {:?}", + universal_location, destination_v4, e + ); + Error::::Internal + })?; + let appendix = vec![ + ReportHolding { + response_info: QueryResponseInfo { + destination: our_location_for_destination.clone(), + max_weight: Weight::zero(), + query_id, + }, + // Include in the report the assets that were not transferred. + assets: AssetFilter::Wild(WildAsset::AllOf { + id: asset_id_v4.clone(), + fun: WildFungibility::Fungible, + }), + }, + // If the asset to transfer failed to transfer, re-put it back into our own account. Otherwise, if the + // transfer succeeded, this asset won't be present in the holding registry, hence this is effectively a + // no-op. + DepositAsset { + assets: AssetFilter::Wild(WildAsset::AllOf { + id: asset_id_v4.clone(), + fun: WildFungibility::Fungible, + }), + beneficiary: our_location_for_destination, + }, + RefundSurplus, + // Refund the XCM fee left to the user. We are purposefully only selecting the XCM fee asset, although + // there should be no cases in which any other assets are present in the holding registry. + DepositAsset { + assets: AssetFilter::Wild(WildAsset::AllOf { + id: remote_asset_fee_v4.id.clone(), + fun: WildFungibility::Fungible, + }), + beneficiary: submitter_as_location.clone(), + }, + ] + .into(); + // Steps performed: + // 1. Withdraw XCM fees from our SA + // 2. Buy execution + // 3. Set the appendix, executed regardless of the outcome of the transfer: + // 3.1 Report back to our chain the assets in the holding registry. This will + // contain either only the XCM fee token in case of successful transfer, or the + // XCM fee token + the amount of funds supposed to be transferred. + // 3.2 Deposit the un-transferred asset (only if the transfer failed) back into + // our account. + // 3.3 Refund any surplus weight. + // 3.4 Deposit the remaining XCM fee assets in the user's account. + // 4. Withdraw the requested asset (this operation should be infallible since we + // have full control of this balance) + // 5. Try to deposit the withdrawn asset into the user's account. This operation + // could fail and the error is handled in the appendix. + let remote_xcm: Xcm<()> = vec![ + WithdrawAsset(remote_asset_fee_v4.clone().into()), + BuyExecution { + weight_limit: WeightLimit::Unlimited, + fees: remote_asset_fee_v4.clone(), + }, + SetAppendix(appendix), + // Because the appendix relies on forwarding the content of the holding registry (there is at the + // moment no other way of detecting failed switches), we need to make sure the assets are present in + // the holding registry before the execution could fail. + // Using `TransferAsset` could result in assets not even being withdrawn, and we would not be able to + // detect the failed switch. Hence, we need to force the transfer to happen in two steps: 1. withdraw + // (which we assume would never fail since we know we own the required assets) + // 2. deposit (which could fail, e.g., if the beneficiary does not have an ED for a sufficient asset). + WithdrawAsset((asset_id_v4.clone(), remote_asset_amount_as_u128).into()), + DepositAsset { + assets: AssetFilter::Definite((asset_id_v4, remote_asset_amount_as_u128).into()), + beneficiary: beneficiary_v4, + }, + ] + .into(); + let xcm_ticket = + validate_send::(destination_v4.clone(), remote_xcm.clone()).map_err(|e| { + log::info!( + "Failed to call `validate_send` for destination {:?} and remote XCM {:?} with error {:?}", + destination_v4, + remote_xcm, + e ); - Error::::Internal + DispatchError::from(Error::::Xcm) })?; - let remote_xcm = Self::compute_xcm_for_switch( - &our_location_for_destination, - &submitter_as_location, - &beneficiary_v4, - remote_asset_amount_as_u128, - &switch_pair_v4.remote_asset_id, - &switch_pair_v4.remote_xcm_fee, - ); - let xcm_ticket = - validate_send::(switch_pair_v4.remote_reserve_location.clone(), remote_xcm.clone()) - .map_err(|e| { - log::info!( - "Failed to call `validate_send` for destination {:?} and remote XCM {:?} with error {:?}", - switch_pair_v4.remote_reserve_location, - remote_xcm, - e - ); - DispatchError::from(Error::::Xcm) - })?; // 8. Call into hook pre-switch checks T::SwitchHooks::pre_local_to_remote_switch(&submitter, &beneficiary, local_asset_amount) @@ -544,7 +629,7 @@ pub mod pallet { // 9. Transfer funds from user to pool let transferred_amount = T::LocalCurrency::transfer( &submitter, - &switch_pair_v4.pool_account, + &switch_pair.pool_account, local_asset_amount, // We don't care if the submitter's account gets dusted, but it should not be killed. Preservation::Protect, @@ -559,24 +644,23 @@ pub mod pallet { } // 10. Take XCM fee from submitter. - let withdrawn_fees = - T::AssetTransactor::withdraw_asset(&switch_pair_v4.remote_xcm_fee, &submitter_as_location, None) - .map_err(|e| { - log::info!( - target: LOG_TARGET, - "Failed to withdraw asset {:?} from location {:?} with error {:?}", - switch_pair_v4.remote_xcm_fee, - submitter_as_location, - e - ); - DispatchError::from(Error::::UserXcmBalance) - })?; - if withdrawn_fees != vec![switch_pair_v4.remote_xcm_fee.clone()].into() { + let withdrawn_fees = T::AssetTransactor::withdraw_asset(&remote_asset_fee_v4, &submitter_as_location, None) + .map_err(|e| { + log::info!( + target: LOG_TARGET, + "Failed to withdraw asset {:?} from location {:?} with error {:?}", + remote_asset_fee_v4, + submitter_as_location, + e + ); + DispatchError::from(Error::::UserXcmBalance) + })?; + if withdrawn_fees != vec![remote_asset_fee_v4.clone()].into() { log::error!( target: LOG_TARGET, "Withdrawn fees {:?} does not match expected fee {:?}.", withdrawn_fees, - switch_pair_v4.remote_xcm_fee + remote_asset_fee_v4 ); return Err(DispatchError::from(Error::::Internal)); } @@ -606,15 +690,7 @@ pub mod pallet { })?; Ok(()) })?; - // 12.2 Update the query ID storage entry. wrapping around the max value since - // it's safe to do so, assuming by the time we wrap the previously pending - // transfers have all been processed. - let query_id = NextQueryId::::mutate(|entry| { - let next = *entry; - *entry = entry.wrapping_add(1); - next - }); - // 12.3 Write the query ID into storage, checking for the very unlikely + // 12.2 Write the query ID into storage, checking for the very unlikely // situation in which one already exists. PendingSwitchConfirmations::::try_mutate(query_id, |entry| { match entry { @@ -637,6 +713,12 @@ pub mod pallet { } } })?; + // 12.3 Update the query ID storage entry. wrapping around the max value since + // it's safe to do so, assuming by the time we wrap the previously pending + // transfers have all been processed. + NextQueryId::::mutate(|entry| { + *entry = entry.wrapping_add(1); + }); // 13. Call into hook post-switch checks T::SwitchHooks::post_local_to_remote_switch_dispatch(&submitter, &beneficiary, local_asset_amount) @@ -756,100 +838,6 @@ impl, I: 'static> Pallet { }) } - /// Compose the XCM message for a local -> remote switch to be sent to the - /// switch pair configured remote location. - /// - /// The message includes an appendix which reports back to this chain the - /// result of the operation by the means of sending the content of the - /// holding registry after the transfer is supposed to have happened. - /// - /// The XCM program is so composed: - /// 1. Withdraw XCM fees from our SA - /// 2. Buy execution - /// 3. Set the appendix, executed regardless of the outcome of the transfer: - /// - /// 3.1 Report back to our chain the assets in the holding registry. - /// This will contain either only the XCM fee token in case of successful - /// transfer, or the XCM fee token + the amount of funds supposed to be - /// transferred. - /// - /// 3.2 Deposit the un-transferred asset (only if the transfer - /// failed) back into our account. - /// - /// 3.3 Refund any surplus weight. - /// - /// 3.4 Deposit the remaining XCM fee assets in the user's account. - /// - /// 4. Withdraw the requested asset (this operation should be infallible - /// since we have full control of this balance) - /// 5. Try to deposit the withdrawn asset into the user's account. This - /// operation could fail and the error is handled in the appendix. - pub fn compute_xcm_for_switch( - inverted_universal_location: &Location, - from: &Location, - to: &Location, - amount: u128, - asset_id: &AssetId, - remote_asset_fee: &Asset, - ) -> Xcm<()> { - let appendix = vec![ - ReportHolding { - response_info: QueryResponseInfo { - destination: inverted_universal_location.clone(), - max_weight: Weight::zero(), - query_id: NextQueryId::::get(), - }, - // Include in the report the assets that were not transferred. - assets: AssetFilter::Wild(WildAsset::AllOf { - id: asset_id.clone(), - fun: WildFungibility::Fungible, - }), - }, - // If the asset to transfer failed to transfer, re-put it back into our own account. Otherwise, if the - // transfer succeeded, this asset won't be present in the holding registry, hence this is effectively a - // no-op. - DepositAsset { - assets: AssetFilter::Wild(WildAsset::AllOf { - id: asset_id.clone(), - fun: WildFungibility::Fungible, - }), - beneficiary: inverted_universal_location.clone(), - }, - RefundSurplus, - // Refund the XCM fee left to the user. We are purposefully only selecting the XCM fee asset, although - // there should be no cases in which any other assets are present in the holding registry. - DepositAsset { - assets: AssetFilter::Wild(WildAsset::AllOf { - id: remote_asset_fee.id.clone(), - fun: WildFungibility::Fungible, - }), - beneficiary: from.clone(), - }, - ] - .into(); - vec![ - WithdrawAsset(remote_asset_fee.clone().into()), - BuyExecution { - weight_limit: WeightLimit::Unlimited, - fees: remote_asset_fee.clone(), - }, - SetAppendix(appendix), - // Because the appendix relies on forwarding the content of the holding registry (there is at the - // moment no other way of detecting failed switches), we need to make sure the assets are present in - // the holding registry before the execution could fail. - // Using `TransferAsset` could result in assets not even being withdrawn, and we would not be able to - // detect the failed switch. Hence, we need to force the transfer to happen in two steps: 1. withdraw - // (which we assume would never fail since we know we own the required assets) - // 2. deposit (which could fail, e.g., if the beneficiary does not have an ED for a sufficient asset). - WithdrawAsset((asset_id.clone(), amount).into()), - DepositAsset { - assets: AssetFilter::Definite((asset_id.clone(), amount).into()), - beneficiary: to.clone(), - }, - ] - .into() - } - // Read the first item in the storage and returns `true` if `Some`, and `false` // otherwise. fn is_any_transaction_pending() -> bool { diff --git a/pallets/pallet-asset-switch/src/switch.rs b/pallets/pallet-asset-switch/src/switch.rs index e5985718a7..dd61e886ff 100644 --- a/pallets/pallet-asset-switch/src/switch.rs +++ b/pallets/pallet-asset-switch/src/switch.rs @@ -19,13 +19,7 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_core::RuntimeDebug; -use sp_std::marker::PhantomData; -use xcm::{ - v4::{Asset, AssetId, Location}, - VersionedAsset, VersionedAssetId, VersionedLocation, -}; - -use crate::{Config, Error, LOG_TARGET}; +use xcm::{VersionedAsset, VersionedAssetId, VersionedLocation}; /// Input information used to generate a `SwitchPairInfo`. #[derive(Encode, Decode, TypeInfo, RuntimeDebug, Clone)] @@ -179,65 +173,3 @@ pub struct UnconfirmedSwitchInfo { pub(crate) to: Destination, pub(crate) amount: Amount, } - -#[allow(dead_code)] -pub(crate) struct SwitchPairInfoV4 { - pub(crate) pool_account: AccountId, - pub(crate) remote_asset_circulating_supply: u128, - pub(crate) remote_asset_ed: u128, - pub(crate) remote_asset_id: AssetId, - pub(crate) remote_asset_total_supply: u128, - pub(crate) remote_reserve_location: Location, - pub(crate) remote_xcm_fee: Asset, - pub(crate) status: SwitchPairStatus, - pub(crate) remote_asset_sovereign_total_balance: u128, - _phantom_data: PhantomData<(T, I)>, -} - -impl TryFrom> for SwitchPairInfoV4 -where - T: Config, - I: 'static, -{ - type Error = Error; - - fn try_from(value: SwitchPairInfo) -> Result { - let value_v4 = Self { - pool_account: value.pool_account, - remote_asset_circulating_supply: value.remote_asset_circulating_supply, - remote_asset_ed: value.remote_asset_ed, - remote_asset_sovereign_total_balance: value.remote_asset_sovereign_total_balance, - remote_asset_total_supply: value.remote_asset_total_supply, - status: value.status, - remote_asset_id: value.remote_asset_id.clone().try_into().map_err(|e| { - log::error!( - target: LOG_TARGET, - "Failed to convert asset ID {:?} into v4 `AssetId` with error {:?}", - value.remote_asset_id, - e - ); - Error::::Internal - })?, - remote_reserve_location: value.remote_reserve_location.clone().try_into().map_err(|e| { - log::error!( - target: LOG_TARGET, - "Failed to convert remote reserve location {:?} into v4 `Location` with error {:?}", - value.remote_reserve_location, - e - ); - Error::::Internal - })?, - remote_xcm_fee: value.remote_xcm_fee.clone().try_into().map_err(|e| { - log::error!( - target: LOG_TARGET, - "Failed to convert remote XCM asset fee {:?} into v4 `Asset` with error {:?}", - value.remote_xcm_fee, - e - ); - Error::::Xcm - })?, - _phantom_data: Default::default(), - }; - Ok(value_v4) - } -} diff --git a/runtime-api/asset-switch/src/lib.rs b/runtime-api/asset-switch/src/lib.rs index d75f858ad0..408c38e3a6 100644 --- a/runtime-api/asset-switch/src/lib.rs +++ b/runtime-api/asset-switch/src/lib.rs @@ -22,17 +22,12 @@ use parity_scale_codec::Codec; use sp_std::vec::Vec; sp_api::decl_runtime_apis! { - /// Runtime API to compute the pool account for a given switch pair ID and remote asset, and to compute the XCM that would be sent to destination for a given switch operation. - #[api_version(2)] - pub trait AssetSwitch where + /// Runtime API to compute the pool account for a given switch pair ID and remote asset. + pub trait AssetSwitch where AssetId: Codec, AccountId: Codec, - Amount: Codec, - Destination: Codec, Error: Codec, - Xcm: Codec, { fn pool_account_id(pair_id: Vec, asset_id: AssetId) -> Result; - fn xcm_for_switch(pair_id: Vec, from: AccountId, to: Destination, amount: Amount) -> Result; } } diff --git a/runtimes/common/src/asset_switch/runtime_api.rs b/runtimes/common/src/asset_switch/runtime_api.rs index c1d0d6ae15..a7dbaaa787 100644 --- a/runtimes/common/src/asset_switch/runtime_api.rs +++ b/runtimes/common/src/asset_switch/runtime_api.rs @@ -23,6 +23,5 @@ use scale_info::TypeInfo; pub enum Error { InvalidInput, SwitchPoolNotFound, - SwitchPoolNotSet, Internal, } diff --git a/runtimes/peregrine/src/lib.rs b/runtimes/peregrine/src/lib.rs index f5ae31cfed..713ae59705 100644 --- a/runtimes/peregrine/src/lib.rs +++ b/runtimes/peregrine/src/lib.rs @@ -51,7 +51,7 @@ use sp_runtime::{ }; use sp_std::{cmp::Ordering, prelude::*}; use sp_version::RuntimeVersion; -use xcm::{v4::Location, VersionedAssetId, VersionedLocation, VersionedXcm}; +use xcm::{v4::Location, VersionedAssetId}; use xcm_builder::{FungiblesAdapter, NoChecking}; use delegation::DelegationAc; @@ -1588,7 +1588,7 @@ impl_runtime_apis! { } } - impl pallet_asset_switch_runtime_api::AssetSwitch> for Runtime { + impl pallet_asset_switch_runtime_api::AssetSwitch for Runtime { fn pool_account_id(pair_id: Vec, asset_id: VersionedAssetId) -> Result { use core::str; use frame_support::traits::PalletInfoAccess; @@ -1606,36 +1606,6 @@ impl_runtime_apis! { _ => Err(AssetSwitchApiError::SwitchPoolNotFound) } } - - fn xcm_for_switch(pair_id: Vec, from: AccountId, to: VersionedLocation, amount: u128) -> Result, AssetSwitchApiError> { - use core::str; - use frame_support::traits::PalletInfoAccess; - use sp_runtime::traits::TryConvert; - use xcm::v4::{AssetId, Asset}; - - let Ok(pair_id_as_string) = str::from_utf8(pair_id.as_slice()) else { - return Err(AssetSwitchApiError::InvalidInput); - }; - - if pair_id_as_string != AssetSwitchPool1::name() { - return Err(AssetSwitchApiError::SwitchPoolNotFound); - } - - let Some(switch_pair) = AssetSwitchPool1::switch_pair() else { - return Err(AssetSwitchApiError::SwitchPoolNotSet); - }; - - let from_v4 = AccountId32ToAccountId32JunctionConverter::try_convert(from).map_err(|_| AssetSwitchApiError::Internal)?; - let to_v4 = Location::try_from(to.clone()).map_err(|_| AssetSwitchApiError::Internal)?; - let our_location_for_destination = { - let universal_location = UniversalLocation::get(); - universal_location.invert_target(&to_v4) - }.map_err(|_| AssetSwitchApiError::Internal)?; - let asset_id_v4 = AssetId::try_from(switch_pair.remote_asset_id).map_err(|_| AssetSwitchApiError::Internal)?; - let remote_asset_fee_v4 = Asset::try_from(switch_pair.remote_xcm_fee).map_err(|_| AssetSwitchApiError::Internal)?; - - Ok(VersionedXcm::V4(AssetSwitchPool1::compute_xcm_for_switch(&our_location_for_destination, &from_v4.into(), &to_v4, amount, &asset_id_v4, &remote_asset_fee_v4))) - } } #[cfg(feature = "runtime-benchmarks")] diff --git a/runtimes/spiritnet/src/lib.rs b/runtimes/spiritnet/src/lib.rs index d0bb014dee..7a9a78f4d3 100644 --- a/runtimes/spiritnet/src/lib.rs +++ b/runtimes/spiritnet/src/lib.rs @@ -51,7 +51,7 @@ use sp_runtime::{ }; use sp_std::{cmp::Ordering, prelude::*}; use sp_version::RuntimeVersion; -use xcm::{v4::Location, VersionedAssetId, VersionedLocation, VersionedXcm}; +use xcm::{v4::Location, VersionedAssetId}; use xcm_builder::{FungiblesAdapter, NoChecking}; use delegation::DelegationAc; @@ -1578,7 +1578,7 @@ impl_runtime_apis! { } } - impl pallet_asset_switch_runtime_api::AssetSwitch> for Runtime { + impl pallet_asset_switch_runtime_api::AssetSwitch for Runtime { fn pool_account_id(pair_id: Vec, asset_id: VersionedAssetId) -> Result { use core::str; use frame_support::traits::PalletInfoAccess; @@ -1596,36 +1596,6 @@ impl_runtime_apis! { _ => Err(AssetSwitchApiError::SwitchPoolNotFound) } } - - fn xcm_for_switch(pair_id: Vec, from: AccountId, to: VersionedLocation, amount: u128) -> Result, AssetSwitchApiError> { - use core::str; - use frame_support::traits::PalletInfoAccess; - use sp_runtime::traits::TryConvert; - use xcm::v4::{AssetId, Asset}; - - let Ok(pair_id_as_string) = str::from_utf8(pair_id.as_slice()) else { - return Err(AssetSwitchApiError::InvalidInput); - }; - - if pair_id_as_string != AssetSwitchPool1::name() { - return Err(AssetSwitchApiError::SwitchPoolNotFound); - } - - let Some(switch_pair) = AssetSwitchPool1::switch_pair() else { - return Err(AssetSwitchApiError::SwitchPoolNotSet); - }; - - let from_v4 = AccountId32ToAccountId32JunctionConverter::try_convert(from).map_err(|_| AssetSwitchApiError::Internal)?; - let to_v4 = Location::try_from(to.clone()).map_err(|_| AssetSwitchApiError::Internal)?; - let our_location_for_destination = { - let universal_location = UniversalLocation::get(); - universal_location.invert_target(&to_v4) - }.map_err(|_| AssetSwitchApiError::Internal)?; - let asset_id_v4 = AssetId::try_from(switch_pair.remote_asset_id).map_err(|_| AssetSwitchApiError::Internal)?; - let remote_asset_fee_v4 = Asset::try_from(switch_pair.remote_xcm_fee).map_err(|_| AssetSwitchApiError::Internal)?; - - Ok(VersionedXcm::V4(AssetSwitchPool1::compute_xcm_for_switch(&our_location_for_destination, &from_v4.into(), &to_v4, amount, &asset_id_v4, &remote_asset_fee_v4))) - } } #[cfg(feature = "runtime-benchmarks")]